mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e699bd0730 | |||
| 961a9379d5 | |||
| ec87b04aff | |||
| aabf752a51 | |||
| afb0674ccb | |||
| ee625232a4 | |||
| 52d8dd41f1 | |||
| be07d2d7a9 | |||
| 0f1b62c2b3 | |||
| c30906a541 | |||
| 3533503323 |
+2
-2
@@ -256,13 +256,13 @@ configure_package_config_file("${PROJECT_NAME}Config.cmake.in"
|
||||
if(HTTPLIB_COMPILE)
|
||||
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
||||
# Example: if you find_package(httplib 0.5.4)
|
||||
# then anything >= 0.5 and <= 1.0 is accepted
|
||||
# then anything >= 0.5.4 and < 0.6 is accepted
|
||||
COMPATIBILITY SameMinorVersion
|
||||
)
|
||||
else()
|
||||
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
||||
# Example: if you find_package(httplib 0.5.4)
|
||||
# then anything >= 0.5 and <= 1.0 is accepted
|
||||
# then anything >= 0.5.4 and < 0.6 is accepted
|
||||
COMPATIBILITY SameMinorVersion
|
||||
# Tells Cmake that it's a header-only lib
|
||||
# Mildly useful for end-users :)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.13.0"
|
||||
#define CPPHTTPLIB_VERSION "0.13.2"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
@@ -668,6 +668,8 @@ using SocketOptions = std::function<void(socket_t sock)>;
|
||||
|
||||
void default_socket_options(socket_t sock);
|
||||
|
||||
const char *status_message(int status);
|
||||
|
||||
namespace detail {
|
||||
|
||||
class MatcherBase {
|
||||
@@ -948,6 +950,7 @@ enum class Error {
|
||||
UnsupportedMultipartBoundaryChars,
|
||||
Compression,
|
||||
ConnectionTimeout,
|
||||
ProxyConnection,
|
||||
|
||||
// For internal use only
|
||||
SSLPeerCouldBeClosed_,
|
||||
@@ -1776,6 +1779,76 @@ inline void default_socket_options(socket_t sock) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline const char *status_message(int status) {
|
||||
switch (status) {
|
||||
case 100: return "Continue";
|
||||
case 101: return "Switching Protocol";
|
||||
case 102: return "Processing";
|
||||
case 103: return "Early Hints";
|
||||
case 200: return "OK";
|
||||
case 201: return "Created";
|
||||
case 202: return "Accepted";
|
||||
case 203: return "Non-Authoritative Information";
|
||||
case 204: return "No Content";
|
||||
case 205: return "Reset Content";
|
||||
case 206: return "Partial Content";
|
||||
case 207: return "Multi-Status";
|
||||
case 208: return "Already Reported";
|
||||
case 226: return "IM Used";
|
||||
case 300: return "Multiple Choice";
|
||||
case 301: return "Moved Permanently";
|
||||
case 302: return "Found";
|
||||
case 303: return "See Other";
|
||||
case 304: return "Not Modified";
|
||||
case 305: return "Use Proxy";
|
||||
case 306: return "unused";
|
||||
case 307: return "Temporary Redirect";
|
||||
case 308: return "Permanent Redirect";
|
||||
case 400: return "Bad Request";
|
||||
case 401: return "Unauthorized";
|
||||
case 402: return "Payment Required";
|
||||
case 403: return "Forbidden";
|
||||
case 404: return "Not Found";
|
||||
case 405: return "Method Not Allowed";
|
||||
case 406: return "Not Acceptable";
|
||||
case 407: return "Proxy Authentication Required";
|
||||
case 408: return "Request Timeout";
|
||||
case 409: return "Conflict";
|
||||
case 410: return "Gone";
|
||||
case 411: return "Length Required";
|
||||
case 412: return "Precondition Failed";
|
||||
case 413: return "Payload Too Large";
|
||||
case 414: return "URI Too Long";
|
||||
case 415: return "Unsupported Media Type";
|
||||
case 416: return "Range Not Satisfiable";
|
||||
case 417: return "Expectation Failed";
|
||||
case 418: return "I'm a teapot";
|
||||
case 421: return "Misdirected Request";
|
||||
case 422: return "Unprocessable Entity";
|
||||
case 423: return "Locked";
|
||||
case 424: return "Failed Dependency";
|
||||
case 425: return "Too Early";
|
||||
case 426: return "Upgrade Required";
|
||||
case 428: return "Precondition Required";
|
||||
case 429: return "Too Many Requests";
|
||||
case 431: return "Request Header Fields Too Large";
|
||||
case 451: return "Unavailable For Legal Reasons";
|
||||
case 501: return "Not Implemented";
|
||||
case 502: return "Bad Gateway";
|
||||
case 503: return "Service Unavailable";
|
||||
case 504: return "Gateway Timeout";
|
||||
case 505: return "HTTP Version Not Supported";
|
||||
case 506: return "Variant Also Negotiates";
|
||||
case 507: return "Insufficient Storage";
|
||||
case 508: return "Loop Detected";
|
||||
case 510: return "Not Extended";
|
||||
case 511: return "Network Authentication Required";
|
||||
|
||||
default:
|
||||
case 500: return "Internal Server Error";
|
||||
}
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline Server &
|
||||
Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
|
||||
@@ -1816,6 +1889,7 @@ inline std::string to_string(const Error error) {
|
||||
return "Unsupported HTTP multipart boundary characters";
|
||||
case Error::Compression: return "Compression failed";
|
||||
case Error::ConnectionTimeout: return "Connection timed out";
|
||||
case Error::ProxyConnection: return "Proxy connection failed";
|
||||
case Error::Unknown: return "Unknown";
|
||||
default: break;
|
||||
}
|
||||
@@ -2356,6 +2430,13 @@ inline std::string trim_copy(const std::string &s) {
|
||||
return s.substr(r.first, r.second - r.first);
|
||||
}
|
||||
|
||||
inline std::string trim_double_quotes_copy(const std::string &s) {
|
||||
if (s.length() >= 2 && s.front() == '"' && s.back() == '"') {
|
||||
return s.substr(1, s.size() - 2);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
inline void split(const char *b, const char *e, char d,
|
||||
std::function<void(const char *, const char *)> fn) {
|
||||
size_t i = 0;
|
||||
@@ -3180,76 +3261,6 @@ find_content_type(const std::string &path,
|
||||
}
|
||||
}
|
||||
|
||||
inline const char *status_message(int status) {
|
||||
switch (status) {
|
||||
case 100: return "Continue";
|
||||
case 101: return "Switching Protocol";
|
||||
case 102: return "Processing";
|
||||
case 103: return "Early Hints";
|
||||
case 200: return "OK";
|
||||
case 201: return "Created";
|
||||
case 202: return "Accepted";
|
||||
case 203: return "Non-Authoritative Information";
|
||||
case 204: return "No Content";
|
||||
case 205: return "Reset Content";
|
||||
case 206: return "Partial Content";
|
||||
case 207: return "Multi-Status";
|
||||
case 208: return "Already Reported";
|
||||
case 226: return "IM Used";
|
||||
case 300: return "Multiple Choice";
|
||||
case 301: return "Moved Permanently";
|
||||
case 302: return "Found";
|
||||
case 303: return "See Other";
|
||||
case 304: return "Not Modified";
|
||||
case 305: return "Use Proxy";
|
||||
case 306: return "unused";
|
||||
case 307: return "Temporary Redirect";
|
||||
case 308: return "Permanent Redirect";
|
||||
case 400: return "Bad Request";
|
||||
case 401: return "Unauthorized";
|
||||
case 402: return "Payment Required";
|
||||
case 403: return "Forbidden";
|
||||
case 404: return "Not Found";
|
||||
case 405: return "Method Not Allowed";
|
||||
case 406: return "Not Acceptable";
|
||||
case 407: return "Proxy Authentication Required";
|
||||
case 408: return "Request Timeout";
|
||||
case 409: return "Conflict";
|
||||
case 410: return "Gone";
|
||||
case 411: return "Length Required";
|
||||
case 412: return "Precondition Failed";
|
||||
case 413: return "Payload Too Large";
|
||||
case 414: return "URI Too Long";
|
||||
case 415: return "Unsupported Media Type";
|
||||
case 416: return "Range Not Satisfiable";
|
||||
case 417: return "Expectation Failed";
|
||||
case 418: return "I'm a teapot";
|
||||
case 421: return "Misdirected Request";
|
||||
case 422: return "Unprocessable Entity";
|
||||
case 423: return "Locked";
|
||||
case 424: return "Failed Dependency";
|
||||
case 425: return "Too Early";
|
||||
case 426: return "Upgrade Required";
|
||||
case 428: return "Precondition Required";
|
||||
case 429: return "Too Many Requests";
|
||||
case 431: return "Request Header Fields Too Large";
|
||||
case 451: return "Unavailable For Legal Reasons";
|
||||
case 501: return "Not Implemented";
|
||||
case 502: return "Bad Gateway";
|
||||
case 503: return "Service Unavailable";
|
||||
case 504: return "Gateway Timeout";
|
||||
case 505: return "HTTP Version Not Supported";
|
||||
case 506: return "Variant Also Negotiates";
|
||||
case 507: return "Insufficient Storage";
|
||||
case 508: return "Loop Detected";
|
||||
case 510: return "Not Extended";
|
||||
case 511: return "Network Authentication Required";
|
||||
|
||||
default:
|
||||
case 500: return "Internal Server Error";
|
||||
}
|
||||
}
|
||||
|
||||
inline bool can_compress_content_type(const std::string &content_type) {
|
||||
using udl::operator""_t;
|
||||
|
||||
@@ -3384,16 +3395,12 @@ inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
|
||||
data += strm_.avail_in;
|
||||
|
||||
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
|
||||
while (strm_.avail_in > 0) {
|
||||
while (strm_.avail_in > 0 && ret == Z_OK) {
|
||||
strm_.avail_out = static_cast<uInt>(buff.size());
|
||||
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
|
||||
auto prev_avail_in = strm_.avail_in;
|
||||
|
||||
ret = inflate(&strm_, Z_NO_FLUSH);
|
||||
|
||||
if (prev_avail_in - strm_.avail_in == 0) { return false; }
|
||||
|
||||
assert(ret != Z_STREAM_ERROR);
|
||||
switch (ret) {
|
||||
case Z_NEED_DICT:
|
||||
@@ -4066,14 +4073,34 @@ inline bool parse_multipart_boundary(const std::string &content_type,
|
||||
if (pos == std::string::npos) { return false; }
|
||||
auto end = content_type.find(';', pos);
|
||||
auto beg = pos + strlen(boundary_keyword);
|
||||
boundary = content_type.substr(beg, end - beg);
|
||||
if (boundary.length() >= 2 && boundary.front() == '"' &&
|
||||
boundary.back() == '"') {
|
||||
boundary = boundary.substr(1, boundary.size() - 2);
|
||||
}
|
||||
boundary = trim_double_quotes_copy(content_type.substr(beg, end - beg));
|
||||
return !boundary.empty();
|
||||
}
|
||||
|
||||
inline void parse_disposition_params(const std::string &s, Params ¶ms) {
|
||||
std::set<std::string> cache;
|
||||
split(s.data(), s.data() + s.size(), ';', [&](const char *b, const char *e) {
|
||||
std::string kv(b, e);
|
||||
if (cache.find(kv) != cache.end()) { return; }
|
||||
cache.insert(kv);
|
||||
|
||||
std::string key;
|
||||
std::string val;
|
||||
split(b, e, '=', [&](const char *b2, const char *e2) {
|
||||
if (key.empty()) {
|
||||
key.assign(b2, e2);
|
||||
} else {
|
||||
val.assign(b2, e2);
|
||||
}
|
||||
});
|
||||
|
||||
if (!key.empty()) {
|
||||
params.emplace(trim_double_quotes_copy((key)),
|
||||
trim_double_quotes_copy((val)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
inline bool parse_range_header(const std::string &s, Ranges &ranges) {
|
||||
#else
|
||||
@@ -4131,11 +4158,6 @@ public:
|
||||
bool parse(const char *buf, size_t n, const ContentReceiver &content_callback,
|
||||
const MultipartContentHeader &header_callback) {
|
||||
|
||||
// TODO: support 'filename*'
|
||||
static const std::regex re_content_disposition(
|
||||
R"~(^Content-Disposition:\s*form-data;\s*name="(.*?)"(?:;\s*filename="(.*?)")?(?:;\s*filename\*=\S+)?\s*$)~",
|
||||
std::regex_constants::icase);
|
||||
|
||||
buf_append(buf, n);
|
||||
|
||||
while (buf_size() > 0) {
|
||||
@@ -4173,10 +4195,40 @@ public:
|
||||
if (start_with_case_ignore(header, header_name)) {
|
||||
file_.content_type = trim_copy(header.substr(header_name.size()));
|
||||
} else {
|
||||
static const std::regex re_content_disposition(
|
||||
R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~",
|
||||
std::regex_constants::icase);
|
||||
|
||||
std::smatch m;
|
||||
if (std::regex_match(header, m, re_content_disposition)) {
|
||||
file_.name = m[1];
|
||||
file_.filename = m[2];
|
||||
Params params;
|
||||
parse_disposition_params(m[1], params);
|
||||
|
||||
auto it = params.find("name");
|
||||
if (it != params.end()) {
|
||||
file_.name = it->second;
|
||||
} else {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
it = params.find("filename");
|
||||
if (it != params.end()) { file_.filename = it->second; }
|
||||
|
||||
it = params.find("filename*");
|
||||
if (it != params.end()) {
|
||||
// Only allow UTF-8 enconnding...
|
||||
static const std::regex re_rfc5987_encoding(
|
||||
R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase);
|
||||
|
||||
std::smatch m2;
|
||||
if (std::regex_match(it->second, m2, re_rfc5987_encoding)) {
|
||||
file_.filename = decode_url(m2[1], false); // override...
|
||||
} else {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
@@ -4217,9 +4269,9 @@ public:
|
||||
buf_erase(crlf_.size());
|
||||
state_ = 1;
|
||||
} else {
|
||||
if (dash_crlf_.size() > buf_size()) { return true; }
|
||||
if (buf_start_with(dash_crlf_)) {
|
||||
buf_erase(dash_crlf_.size());
|
||||
if (dash_.size() > buf_size()) { return true; }
|
||||
if (buf_start_with(dash_)) {
|
||||
buf_erase(dash_.size());
|
||||
is_valid_ = true;
|
||||
buf_erase(buf_size()); // Remove epilogue
|
||||
} else {
|
||||
@@ -4252,7 +4304,6 @@ private:
|
||||
|
||||
const std::string dash_ = "--";
|
||||
const std::string crlf_ = "\r\n";
|
||||
const std::string dash_crlf_ = "--\r\n";
|
||||
std::string boundary_;
|
||||
std::string dash_boundary_crlf_;
|
||||
std::string crlf_dash_boundary_;
|
||||
@@ -5275,7 +5326,7 @@ inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) {
|
||||
}
|
||||
|
||||
inline bool PathParamsMatcher::match(Request &request) const {
|
||||
request.matches = {};
|
||||
request.matches = std::smatch();
|
||||
request.path_params.clear();
|
||||
request.path_params.reserve(param_names_.size());
|
||||
|
||||
@@ -5696,7 +5747,7 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
||||
detail::BufferStream bstrm;
|
||||
|
||||
if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
|
||||
detail::status_message(res.status))) {
|
||||
status_message(res.status))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -6262,15 +6313,10 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
if (!line_reader.getline()) { return false; }
|
||||
|
||||
Request req;
|
||||
|
||||
Response res;
|
||||
|
||||
res.version = "HTTP/1.1";
|
||||
|
||||
for (const auto &header : default_headers_) {
|
||||
if (res.headers.find(header.first) == res.headers.end()) {
|
||||
res.headers.insert(header);
|
||||
}
|
||||
}
|
||||
res.headers = default_headers_;
|
||||
|
||||
#ifdef _WIN32
|
||||
// TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL).
|
||||
@@ -6337,7 +6383,7 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
case 100:
|
||||
case 417:
|
||||
strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
|
||||
detail::status_message(status));
|
||||
status_message(status));
|
||||
break;
|
||||
default: return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
@@ -6704,6 +6750,21 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
|
||||
|
||||
if (!ret) { return false; }
|
||||
|
||||
if (res.get_header_value("Connection") == "close" ||
|
||||
(res.version == "HTTP/1.0" && res.reason != "Connection established")) {
|
||||
// TODO this requires a not-entirely-obvious chain of calls to be correct
|
||||
// for this to be safe.
|
||||
|
||||
// This is safe to call because handle_request is only called by send_
|
||||
// which locks the request mutex during the process. It would be a bug
|
||||
// to call it from a different thread since it's a thread-safety issue
|
||||
// to do these things to the socket if another thread is using the socket.
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
shutdown_ssl(socket_, true);
|
||||
shutdown_socket(socket_);
|
||||
close_socket(socket_);
|
||||
}
|
||||
|
||||
if (300 < res.status && res.status < 400 && follow_location_) {
|
||||
req = req_save;
|
||||
ret = redirect(req, res, error);
|
||||
@@ -7118,24 +7179,6 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
}
|
||||
}
|
||||
|
||||
if (res.get_header_value("Connection") == "close" ||
|
||||
(res.version == "HTTP/1.0" && res.reason != "Connection established")) {
|
||||
// TODO this requires a not-entirely-obvious chain of calls to be correct
|
||||
// for this to be safe. Maybe a code refactor (such as moving this out to
|
||||
// the send function and getting rid of the recursiveness of the mutex)
|
||||
// could make this more obvious.
|
||||
|
||||
// This is safe to call because process_request is only called by
|
||||
// handle_request which is only called by send, which locks the request
|
||||
// mutex during the process. It would be a bug to call it from a different
|
||||
// thread since it's a thread-safety issue to do these things to the socket
|
||||
// if another thread is using the socket.
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
shutdown_ssl(socket_, true);
|
||||
shutdown_socket(socket_);
|
||||
close_socket(socket_);
|
||||
}
|
||||
|
||||
// Log
|
||||
if (logger_) { logger_(req, res); }
|
||||
|
||||
@@ -8243,14 +8286,14 @@ inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
|
||||
inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
||||
bool &success, Error &error) {
|
||||
success = true;
|
||||
Response res2;
|
||||
Response proxy_res;
|
||||
if (!detail::process_client_socket(
|
||||
socket.sock, read_timeout_sec_, read_timeout_usec_,
|
||||
write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
|
||||
Request req2;
|
||||
req2.method = "CONNECT";
|
||||
req2.path = host_and_port_;
|
||||
return process_request(strm, req2, res2, false, error);
|
||||
return process_request(strm, req2, proxy_res, false, error);
|
||||
})) {
|
||||
// Thread-safe to close everything because we are assuming there are no
|
||||
// requests in flight
|
||||
@@ -8261,12 +8304,12 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (res2.status == 407) {
|
||||
if (proxy_res.status == 407) {
|
||||
if (!proxy_digest_auth_username_.empty() &&
|
||||
!proxy_digest_auth_password_.empty()) {
|
||||
std::map<std::string, std::string> auth;
|
||||
if (detail::parse_www_authenticate(res2, auth, true)) {
|
||||
Response res3;
|
||||
if (detail::parse_www_authenticate(proxy_res, auth, true)) {
|
||||
proxy_res = Response();
|
||||
if (!detail::process_client_socket(
|
||||
socket.sock, read_timeout_sec_, read_timeout_usec_,
|
||||
write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
|
||||
@@ -8277,7 +8320,7 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
||||
req3, auth, 1, detail::random_string(10),
|
||||
proxy_digest_auth_username_, proxy_digest_auth_password_,
|
||||
true));
|
||||
return process_request(strm, req3, res3, false, error);
|
||||
return process_request(strm, req3, proxy_res, false, error);
|
||||
})) {
|
||||
// Thread-safe to close everything because we are assuming there are
|
||||
// no requests in flight
|
||||
@@ -8288,12 +8331,24 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res = res2;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If status code is not 200, proxy request is failed.
|
||||
// Set error to ProxyConnection and return proxy response
|
||||
// as the response of the request
|
||||
if (proxy_res.status != 200)
|
||||
{
|
||||
error = Error::ProxyConnection;
|
||||
res = std::move(proxy_res);
|
||||
// Thread-safe to close everything because we are assuming there are
|
||||
// no requests in flight
|
||||
shutdown_ssl(socket, true);
|
||||
shutdown_socket(socket);
|
||||
close_socket(socket);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -86,6 +86,12 @@ subdir(join_paths('www', 'dir'))
|
||||
subdir(join_paths('www2', 'dir'))
|
||||
subdir(join_paths('www3', 'dir'))
|
||||
|
||||
# GoogleTest 1.13.0 requires C++14
|
||||
test_options = []
|
||||
if gtest_dep.version().version_compare('>=1.13.0')
|
||||
test_options += 'cpp_std=c++14'
|
||||
endif
|
||||
|
||||
test(
|
||||
'main',
|
||||
executable(
|
||||
@@ -94,7 +100,8 @@ test(
|
||||
dependencies: [
|
||||
cpp_httplib_dep,
|
||||
gtest_dep
|
||||
]
|
||||
],
|
||||
override_options: test_options
|
||||
),
|
||||
depends: [
|
||||
key_pem,
|
||||
|
||||
+167
@@ -3330,6 +3330,59 @@ TEST(GzipDecompressor, ChunkedDecompression) {
|
||||
ASSERT_EQ(data, decompressed_data);
|
||||
}
|
||||
|
||||
TEST(GzipDecompressor, DeflateDecompression) {
|
||||
std::string original_text = "Raw deflate without gzip";
|
||||
unsigned char data[32] = {0x78, 0x9C, 0x0B, 0x4A, 0x2C, 0x57, 0x48, 0x49,
|
||||
0x4D, 0xCB, 0x49, 0x2C, 0x49, 0x55, 0x28, 0xCF,
|
||||
0x2C, 0xC9, 0xC8, 0x2F, 0x2D, 0x51, 0x48, 0xAF,
|
||||
0xCA, 0x2C, 0x00, 0x00, 0x6F, 0x98, 0x09, 0x2E};
|
||||
std::string compressed_data(data, data + sizeof(data) / sizeof(data[0]));
|
||||
|
||||
std::string decompressed_data;
|
||||
{
|
||||
httplib::detail::gzip_decompressor decompressor;
|
||||
|
||||
bool result = decompressor.decompress(
|
||||
compressed_data.data(), compressed_data.size(),
|
||||
[&](const char *decompressed_data_chunk,
|
||||
size_t decompressed_data_chunk_size) {
|
||||
decompressed_data.insert(decompressed_data.size(),
|
||||
decompressed_data_chunk,
|
||||
decompressed_data_chunk_size);
|
||||
return true;
|
||||
});
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
ASSERT_EQ(original_text, decompressed_data);
|
||||
}
|
||||
|
||||
TEST(GzipDecompressor, DeflateDecompressionTrailingBytes) {
|
||||
std::string original_text = "Raw deflate without gzip";
|
||||
unsigned char data[40] = {0x78, 0x9C, 0x0B, 0x4A, 0x2C, 0x57, 0x48, 0x49,
|
||||
0x4D, 0xCB, 0x49, 0x2C, 0x49, 0x55, 0x28, 0xCF,
|
||||
0x2C, 0xC9, 0xC8, 0x2F, 0x2D, 0x51, 0x48, 0xAF,
|
||||
0xCA, 0x2C, 0x00, 0x00, 0x6F, 0x98, 0x09, 0x2E,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
std::string compressed_data(data, data + sizeof(data) / sizeof(data[0]));
|
||||
|
||||
std::string decompressed_data;
|
||||
{
|
||||
httplib::detail::gzip_decompressor decompressor;
|
||||
|
||||
bool result = decompressor.decompress(
|
||||
compressed_data.data(), compressed_data.size(),
|
||||
[&](const char *decompressed_data_chunk,
|
||||
size_t decompressed_data_chunk_size) {
|
||||
decompressed_data.insert(decompressed_data.size(),
|
||||
decompressed_data_chunk,
|
||||
decompressed_data_chunk_size);
|
||||
return true;
|
||||
});
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
ASSERT_EQ(original_text, decompressed_data);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
TEST(GzipDecompressor, LargeRandomData) {
|
||||
|
||||
@@ -6052,6 +6105,120 @@ TEST(MultipartFormDataTest, PutInvalidBoundaryChars) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, AlternateFilename) {
|
||||
auto handled = false;
|
||||
|
||||
Server svr;
|
||||
svr.Post("/test", [&](const Request &req, Response &res) {
|
||||
ASSERT_EQ(3u, req.files.size());
|
||||
|
||||
auto it = req.files.begin();
|
||||
ASSERT_EQ("file1", it->second.name);
|
||||
ASSERT_EQ("A.txt", it->second.filename);
|
||||
ASSERT_EQ("text/plain", it->second.content_type);
|
||||
ASSERT_EQ("Content of a.txt.\r\n", it->second.content);
|
||||
|
||||
++it;
|
||||
ASSERT_EQ("file2", it->second.name);
|
||||
ASSERT_EQ("a.html", it->second.filename);
|
||||
ASSERT_EQ("text/html", it->second.content_type);
|
||||
ASSERT_EQ("<!DOCTYPE html><title>Content of a.html.</title>\r\n",
|
||||
it->second.content);
|
||||
|
||||
++it;
|
||||
ASSERT_EQ("text", it->second.name);
|
||||
ASSERT_EQ("", it->second.filename);
|
||||
ASSERT_EQ("", it->second.content_type);
|
||||
ASSERT_EQ("text default", it->second.content);
|
||||
|
||||
res.set_content("ok", "text/plain");
|
||||
|
||||
handled = true;
|
||||
});
|
||||
|
||||
thread t = thread([&] { svr.listen(HOST, PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
ASSERT_TRUE(handled);
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
auto req = "POST /test HTTP/1.1\r\n"
|
||||
"Content-Type: multipart/form-data;boundary=--------\r\n"
|
||||
"Content-Length: 399\r\n"
|
||||
"\r\n"
|
||||
"----------\r\n"
|
||||
"Content-Disposition: form-data; name=\"text\"\r\n"
|
||||
"\r\n"
|
||||
"text default\r\n"
|
||||
"----------\r\n"
|
||||
"Content-Disposition: form-data; filename*=\"UTF-8''\%41.txt\"; "
|
||||
"filename=\"a.txt\"; name=\"file1\"\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"\r\n"
|
||||
"Content of a.txt.\r\n"
|
||||
"\r\n"
|
||||
"----------\r\n"
|
||||
"Content-Disposition: form-data; name=\"file2\" ;filename = "
|
||||
"\"a.html\"\r\n"
|
||||
"Content-Type: text/html\r\n"
|
||||
"\r\n"
|
||||
"<!DOCTYPE html><title>Content of a.html.</title>\r\n"
|
||||
"\r\n"
|
||||
"------------\r\n";
|
||||
|
||||
ASSERT_TRUE(send_request(1, req));
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, CloseDelimiterWithoutCRLF) {
|
||||
auto handled = false;
|
||||
|
||||
Server svr;
|
||||
svr.Post("/test", [&](const Request &req, Response &) {
|
||||
ASSERT_EQ(2u, req.files.size());
|
||||
|
||||
auto it = req.files.begin();
|
||||
ASSERT_EQ("text1", it->second.name);
|
||||
ASSERT_EQ("text1", it->second.content);
|
||||
|
||||
++it;
|
||||
ASSERT_EQ("text2", it->second.name);
|
||||
ASSERT_EQ("text2", it->second.content);
|
||||
|
||||
handled = true;
|
||||
});
|
||||
|
||||
thread t = thread([&] { svr.listen(HOST, PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
ASSERT_TRUE(handled);
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
auto req = "POST /test HTTP/1.1\r\n"
|
||||
"Content-Type: multipart/form-data;boundary=--------\r\n"
|
||||
"Content-Length: 146\r\n"
|
||||
"\r\n----------\r\n"
|
||||
"Content-Disposition: form-data; name=\"text1\"\r\n"
|
||||
"\r\n"
|
||||
"text1"
|
||||
"\r\n----------\r\n"
|
||||
"Content-Disposition: form-data; name=\"text2\"\r\n"
|
||||
"\r\n"
|
||||
"text2"
|
||||
"\r\n------------";
|
||||
|
||||
std::string resonse;
|
||||
ASSERT_TRUE(send_request(1, req, &resonse));
|
||||
ASSERT_EQ("200", resonse.substr(9, 3));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
Reference in New Issue
Block a user