Compare commits

..

13 Commits

Author SHA1 Message Date
yhirose b324921c1a Release v0.10.1 2021-12-24 21:01:06 -05:00
yhirose 63f72caf30 Fix "Issue 42689 in oss-fuzz: cpp-httplib:server_fuzzer: Timeout in server_fuzzer" 2021-12-24 20:58:09 -05:00
yhirose 99ac17b90a Fix #1140 2021-12-23 23:19:14 -05:00
yhirose 4b0ed9ee88 Release v0.10.0 2021-12-21 18:15:41 -05:00
yhirose 20056f6cda Update test.cc 2021-12-19 14:19:10 -05:00
yhirose 3b35279b16 Added SSLServer::ssl_context() 2021-12-18 00:21:41 -05:00
yhirose 27deb44df5 Update SSL related code 2021-12-18 00:15:38 -05:00
yhirose 24a3ef949b Performance improvement for multipart form data file upload. 2021-12-18 00:15:10 -05:00
yhirose bc3e098964 Updated .gitignore 2021-12-17 22:36:02 -05:00
yhirose c247dcdd7b Added uploader.sh 2021-12-17 22:34:00 -05:00
yhirose 793ae9855e Fix #1041 (#1132)
* Fix #1041

* Fixed problem with is_socket_alive

* Adjust the way to check if the sockt is still alive.

* Revert "Adjust the way to check if the sockt is still alive."

This reverts commit 6c673b21e5.

* Adjust is_socket_alive according to the code review
2021-12-16 21:06:17 -05:00
yhirose 9fa426d51b Added more fuzzing corpus 2021-12-14 18:35:20 -05:00
yhirose cec6288a99 Resolve #1131 2021-12-14 07:58:21 -05:00
9 changed files with 207 additions and 112 deletions
+2
View File
@@ -13,10 +13,12 @@ example/*.pem
test/httplib.cc
test/httplib.h
test/test
test/server_fuzzer
test/test_proxy
test/test_split
test/test.xcodeproj/xcuser*
test/test.xcodeproj/*/xcuser*
test/*.o
test/*.pem
test/*.srl
+6
View File
@@ -0,0 +1,6 @@
#/usr/bin/env bash
for i in {1..10000}
do
echo "#### $i ####"
curl -X POST -F image_file=@$1 http://localhost:1234/post > /dev/null
done
+135 -93
View File
@@ -60,6 +60,10 @@
#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
#endif
#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH
#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192
#endif
#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT
#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
#endif
@@ -95,6 +99,10 @@
#define CPPHTTPLIB_SEND_FLAGS 0
#endif
#ifndef CPPHTTPLIB_LISTEN_BACKLOG
#define CPPHTTPLIB_LISTEN_BACKLOG 5
#endif
/*
* Headers
*/
@@ -1378,6 +1386,8 @@ public:
bool is_valid() const override;
SSL_CTX *ssl_context() const;
private:
bool process_and_close_socket(socket_t sock) override;
@@ -2333,6 +2343,17 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
#endif
}
inline bool is_socket_alive(socket_t sock) {
const auto val = detail::select_read(sock, 0, 0);
if (val == 0) {
return true;
} else if (val < 0 && errno == EBADF) {
return false;
}
char buf[1];
return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0;
}
class SocketStream : public Stream {
public:
SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
@@ -2859,7 +2880,8 @@ inline const char *status_message(int status) {
}
inline bool can_compress_content_type(const std::string &content_type) {
return (!content_type.find("text/") && content_type != "text/event-stream") ||
return (!content_type.rfind("text/", 0) &&
content_type != "text/event-stream") ||
content_type == "image/svg+xml" ||
content_type == "application/javascript" ||
content_type == "application/json" ||
@@ -3160,6 +3182,8 @@ inline bool read_headers(Stream &strm, Headers &headers) {
continue; // Skip invalid line.
}
if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
// Exclude CRLF
auto end = line_reader.ptr() + line_reader.size() - 2;
@@ -3666,17 +3690,15 @@ public:
static const std::string dash_ = "--";
static const std::string crlf_ = "\r\n";
buf_.append(buf, n); // TODO: performance improvement
buf_append(buf, n);
while (!buf_.empty()) {
while (buf_size() > 0) {
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) { return false; }
buf_.erase(0, pattern.size());
off_ += pattern.size();
if (pattern.size() > buf_size()) { return true; }
if (!buf_start_with(pattern)) { return false; }
buf_erase(pattern.size());
state_ = 1;
break;
}
@@ -3686,22 +3708,22 @@ public:
break;
}
case 2: { // Headers
auto pos = buf_.find(crlf_);
while (pos != std::string::npos) {
auto pos = buf_find(crlf_);
if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
while (pos < buf_size()) {
// Empty line
if (pos == 0) {
if (!header_callback(file_)) {
is_valid_ = false;
return false;
}
buf_.erase(0, crlf_.size());
off_ += crlf_.size();
buf_erase(crlf_.size());
state_ = 3;
break;
}
static const std::string header_name = "content-type:";
const auto header = buf_.substr(0, pos);
const auto header = buf_head(pos);
if (start_with_case_ignore(header, header_name)) {
file_.content_type = trim_copy(header.substr(header_name.size()));
} else {
@@ -3712,9 +3734,8 @@ public:
}
}
buf_.erase(0, pos + crlf_.size());
off_ += pos + crlf_.size();
pos = buf_.find(crlf_);
buf_erase(pos + crlf_.size());
pos = buf_find(crlf_);
}
if (state_ != 3) { return true; }
break;
@@ -3722,56 +3743,51 @@ public:
case 3: { // Body
{
auto pattern = crlf_ + dash_;
if (pattern.size() > buf_.size()) { return true; }
if (pattern.size() > buf_size()) { return true; }
auto pos = find_string(buf_, pattern);
auto pos = buf_find(pattern);
if (!content_callback(buf_.data(), pos)) {
if (!content_callback(buf_data(), pos)) {
is_valid_ = false;
return false;
}
off_ += pos;
buf_.erase(0, pos);
buf_erase(pos);
}
{
auto pattern = crlf_ + dash_ + boundary_;
if (pattern.size() > buf_.size()) { return true; }
if (pattern.size() > buf_size()) { return true; }
auto pos = buf_.find(pattern);
if (pos != std::string::npos) {
if (!content_callback(buf_.data(), pos)) {
auto pos = buf_find(pattern);
if (pos < buf_size()) {
if (!content_callback(buf_data(), pos)) {
is_valid_ = false;
return false;
}
off_ += pos + pattern.size();
buf_.erase(0, pos + pattern.size());
buf_erase(pos + pattern.size());
state_ = 4;
} else {
if (!content_callback(buf_.data(), pattern.size())) {
if (!content_callback(buf_data(), pattern.size())) {
is_valid_ = false;
return false;
}
off_ += pattern.size();
buf_.erase(0, pattern.size());
buf_erase(pattern.size());
}
}
break;
}
case 4: { // Boundary
if (crlf_.size() > buf_.size()) { return true; }
if (buf_.compare(0, crlf_.size(), crlf_) == 0) {
buf_.erase(0, crlf_.size());
off_ += crlf_.size();
if (crlf_.size() > buf_size()) { return true; }
if (buf_start_with(crlf_)) {
buf_erase(crlf_.size());
state_ = 1;
} else {
auto pattern = dash_ + crlf_;
if (pattern.size() > buf_.size()) { return true; }
if (buf_.compare(0, pattern.size(), pattern) == 0) {
buf_.erase(0, pattern.size());
off_ += pattern.size();
if (pattern.size() > buf_size()) { return true; }
if (buf_start_with(pattern)) {
buf_erase(pattern.size());
is_valid_ = true;
state_ = 5;
} else {
@@ -3806,41 +3822,78 @@ private:
return true;
}
bool start_with(const std::string &a, size_t off,
std::string boundary_;
size_t state_ = 0;
bool is_valid_ = false;
MultipartFormData file_;
// Buffer
bool start_with(const std::string &a, size_t spos, size_t epos,
const std::string &b) const {
if (a.size() - off < b.size()) { return false; }
if (epos - spos < b.size()) { return false; }
for (size_t i = 0; i < b.size(); i++) {
if (a[i + off] != b[i]) { return false; }
if (a[i + spos] != b[i]) { return false; }
}
return true;
}
size_t find_string(const std::string &s, const std::string &pattern) const {
auto c = pattern.front();
size_t buf_size() const { return buf_epos_ - buf_spos_; }
size_t off = 0;
while (off < s.size()) {
auto pos = s.find(c, off);
if (pos == std::string::npos) { return s.size(); }
const char *buf_data() const { return &buf_[buf_spos_]; }
auto rem = s.size() - pos;
if (pattern.size() > rem) { return pos; }
std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); }
if (start_with(s, pos, pattern)) { return pos; }
bool buf_start_with(const std::string &s) const {
return start_with(buf_, buf_spos_, buf_epos_, s);
}
size_t buf_find(const std::string &s) const {
auto c = s.front();
size_t off = buf_spos_;
while (off < buf_epos_) {
auto pos = off;
while (true) {
if (pos == buf_epos_) { return buf_size(); }
if (buf_[pos] == c) { break; }
pos++;
}
auto remaining_size = buf_epos_ - pos;
if (s.size() > remaining_size) { return buf_size(); }
if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; }
off = pos + 1;
}
return s.size();
return buf_size();
}
std::string boundary_;
void buf_append(const char *data, size_t n) {
auto remaining_size = buf_size();
if (remaining_size > 0 && buf_spos_ > 0) {
for (size_t i = 0; i < remaining_size; i++) {
buf_[i] = buf_[buf_spos_ + i];
}
}
buf_spos_ = 0;
buf_epos_ = remaining_size;
if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); }
for (size_t i = 0; i < n; i++) {
buf_[buf_epos_ + i] = data[i];
}
buf_epos_ += n;
}
void buf_erase(size_t size) { buf_spos_ += size; }
std::string buf_;
size_t state_ = 0;
bool is_valid_ = false;
size_t off_ = 0;
MultipartFormData file_;
size_t buf_spos_ = 0;
size_t buf_epos_ = 0;
};
inline std::string to_lower(const char *beg, const char *end) {
@@ -4303,7 +4356,7 @@ inline size_t Request::get_param_value_count(const char *key) const {
inline bool Request::is_multipart_form_data() const {
const auto &content_type = get_header_value("Content-Type");
return !content_type.find("multipart/form-data");
return !content_type.rfind("multipart/form-data", 0);
}
inline bool Request::has_file(const char *key) const {
@@ -5107,9 +5160,7 @@ Server::create_server_socket(const char *host, int port, int socket_flags,
if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
return false;
}
if (::listen(sock, 5)) { // Listen through 5 channels
return false;
}
if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; }
return true;
});
}
@@ -5719,13 +5770,14 @@ inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
{
std::lock_guard<std::mutex> guard(socket_mutex_);
// Set this to false immediately - if it ever gets set to true by the end of
// the request, we know another thread instructed us to close the socket.
socket_should_be_closed_when_request_is_done_ = false;
auto is_alive = false;
if (socket_.is_open()) {
is_alive = detail::select_write(socket_.sock, 0, 0) > 0;
is_alive = detail::is_socket_alive(socket_.sock);
if (!is_alive) {
// Attempt to avoid sigpipe by shutting down nongracefully if it seems
// like the other side has already closed the connection Also, there
@@ -5982,7 +6034,7 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); }
if (!req.has_header("User-Agent")) {
req.headers.emplace("User-Agent", "cpp-httplib/0.9.10");
req.headers.emplace("User-Agent", "cpp-httplib/0.10.1");
}
if (req.body.empty()) {
@@ -7016,9 +7068,9 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
auto err = SSL_get_error(ssl_, ret);
int n = 1000;
#ifdef _WIN32
while (--n >= 0 &&
(err == SSL_ERROR_WANT_READ ||
err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT)) {
while (--n >= 0 && (err == SSL_ERROR_WANT_READ ||
(err == SSL_ERROR_SYSCALL &&
WSAGetLastError() == WSAETIMEDOUT))) {
#else
while (--n >= 0 && err == SSL_ERROR_WANT_READ) {
#endif
@@ -7046,9 +7098,9 @@ inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
auto err = SSL_get_error(ssl_, ret);
int n = 1000;
#ifdef _WIN32
while (--n >= 0 &&
(err == SSL_ERROR_WANT_WRITE ||
err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT)) {
while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE ||
(err == SSL_ERROR_SYSCALL &&
WSAGetLastError() == WSAETIMEDOUT))) {
#else
while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) {
#endif
@@ -7082,17 +7134,14 @@ static SSLInit sslinit_;
inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
const char *client_ca_cert_file_path,
const char *client_ca_cert_dir_path) {
ctx_ = SSL_CTX_new(TLS_method());
ctx_ = SSL_CTX_new(TLS_server_method());
if (ctx_) {
SSL_CTX_set_options(ctx_,
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
SSL_OP_NO_COMPRESSION |
SSL_OP_NO_COMPRESSION |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
// auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
// SSL_CTX_set_tmp_ecdh(ctx_, ecdh);
// EC_KEY_free(ecdh);
SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) !=
@@ -7100,46 +7149,35 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
SSL_CTX_free(ctx_);
ctx_ = nullptr;
} else if (client_ca_cert_file_path || client_ca_cert_dir_path) {
// if (client_ca_cert_file_path) {
// auto list = SSL_load_client_CA_file(client_ca_cert_file_path);
// SSL_CTX_set_client_CA_list(ctx_, list);
// }
SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path,
client_ca_cert_dir_path);
SSL_CTX_set_verify(
ctx_,
SSL_VERIFY_PEER |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE,
nullptr);
ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
}
}
}
inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
X509_STORE *client_ca_cert_store) {
ctx_ = SSL_CTX_new(SSLv23_server_method());
ctx_ = SSL_CTX_new(TLS_server_method());
if (ctx_) {
SSL_CTX_set_options(ctx_,
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
SSL_OP_NO_COMPRESSION |
SSL_OP_NO_COMPRESSION |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
SSL_CTX_free(ctx_);
ctx_ = nullptr;
} else if (client_ca_cert_store) {
SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
SSL_CTX_set_verify(
ctx_,
SSL_VERIFY_PEER |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE,
nullptr);
ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
}
}
}
@@ -7161,6 +7199,8 @@ inline SSLServer::~SSLServer() {
inline bool SSLServer::is_valid() const { return ctx_; }
inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; }
inline bool SSLServer::process_and_close_socket(socket_t sock) {
auto ssl = detail::ssl_new(
sock, ctx_, ctx_mutex_,
@@ -7204,12 +7244,13 @@ inline SSLClient::SSLClient(const std::string &host, int port,
const std::string &client_cert_path,
const std::string &client_key_path)
: ClientImpl(host, port, client_cert_path, client_key_path) {
ctx_ = SSL_CTX_new(SSLv23_client_method());
ctx_ = SSL_CTX_new(TLS_client_method());
detail::split(&host_[0], &host_[host_.size()], '.',
[&](const char *b, const char *e) {
host_components_.emplace_back(std::string(b, e));
});
if (!client_cert_path.empty() && !client_key_path.empty()) {
if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(),
SSL_FILETYPE_PEM) != 1 ||
@@ -7224,12 +7265,13 @@ inline SSLClient::SSLClient(const std::string &host, int port,
inline SSLClient::SSLClient(const std::string &host, int port,
X509 *client_cert, EVP_PKEY *client_key)
: ClientImpl(host, port) {
ctx_ = SSL_CTX_new(SSLv23_client_method());
ctx_ = SSL_CTX_new(TLS_client_method());
detail::split(&host_[0], &host_[host_.size()], '.',
[&](const char *b, const char *e) {
host_components_.emplace_back(std::string(b, e));
});
if (client_cert != nullptr && client_key != nullptr) {
if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
Binary file not shown.
+64 -19
View File
@@ -750,7 +750,7 @@ TEST(SpecifyServerIPAddressTest, AnotherHostname_Online) {
auto host = "google.com";
auto another_host = "example.com";
auto wrong_ip = "0.0.0.0";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(host);
#else
@@ -766,7 +766,7 @@ TEST(SpecifyServerIPAddressTest, AnotherHostname_Online) {
TEST(SpecifyServerIPAddressTest, RealHostname_Online) {
auto host = "google.com";
auto wrong_ip = "0.0.0.0";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(host);
#else
@@ -1195,12 +1195,13 @@ TEST(ErrorHandlerTest, ContentLength) {
TEST(ExceptionHandlerTest, ContentLength) {
Server svr;
svr.set_exception_handler(
[](const Request & /*req*/, Response &res, std::exception & /*e*/) {
res.status = 500;
res.set_content("abcdefghijklmnopqrstuvwxyz",
"text/html"); // <= Content-Length still 13
});
svr.set_exception_handler([](const Request & /*req*/, Response &res,
std::exception &e) {
EXPECT_EQ("abc", std::string(e.what()));
res.status = 500;
res.set_content("abcdefghijklmnopqrstuvwxyz",
"text/html"); // <= Content-Length still 13 at this point
});
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
res.set_content("Hello World!\n", "text/plain");
@@ -1212,15 +1213,28 @@ TEST(ExceptionHandlerTest, ContentLength) {
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
{
for (size_t i = 0; i < 10; i++) {
Client cli(HOST, PORT);
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
EXPECT_EQ(500, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ("26", res->get_header_value("Content-Length"));
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
for (size_t j = 0; j < 100; j++) {
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
EXPECT_EQ(500, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ("26", res->get_header_value("Content-Length"));
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
}
cli.set_keep_alive(true);
for (size_t j = 0; j < 100; j++) {
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
EXPECT_EQ(500, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ("26", res->get_header_value("Content-Length"));
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
}
}
svr.stop();
@@ -3625,10 +3639,11 @@ TEST(StreamingTest, NoContentLengthStreaming) {
auto get_thread = std::thread([&client]() {
std::string s;
auto res = client.Get("/stream", [&s](const char *data, size_t len) -> bool {
s += std::string(data, len);
return true;
});
auto res =
client.Get("/stream", [&s](const char *data, size_t len) -> bool {
s += std::string(data, len);
return true;
});
EXPECT_EQ("aaabbb", s);
});
@@ -3756,6 +3771,36 @@ TEST(KeepAliveTest, ReadTimeout) {
ASSERT_FALSE(svr.is_running());
}
TEST(KeepAliveTest, Issue1041) {
const auto resourcePath = "/hi";
Server svr;
svr.set_keep_alive_timeout(3);
svr.Get(resourcePath, [](const httplib::Request &, httplib::Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto a2 = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
std::this_thread::sleep_for(std::chrono::milliseconds(200));
Client cli(HOST, PORT);
cli.set_keep_alive(true);
auto result = cli.Get(resourcePath);
ASSERT_TRUE(result);
EXPECT_EQ(200, result->status);
std::this_thread::sleep_for(std::chrono::seconds(5));
result = cli.Get(resourcePath);
ASSERT_TRUE(result);
EXPECT_EQ(200, result->status);
svr.stop();
a2.wait();
}
TEST(ClientProblemDetectionTest, ContentProvider) {
Server svr;