Compare commits

..

10 Commits

Author SHA1 Message Date
372046933 e1133a2dcb std::tolower is undefined if the argument's value is neither representable as unsigned char nor equal to EOF (#761)
Co-authored-by: taoxu <taoxu@bilibili.com>
2020-11-19 09:21:40 -05:00
yhirose e273fec93c Fixed Visual Studio setup 2020-11-19 09:17:59 -05:00
yhirose 95d0b073bd Fix #754 2020-11-15 08:13:08 -05:00
vawen 9c7d841b37 Fix: #746 Compile error under VS2015 (#747)
Co-authored-by: jigarcia@vaxtor.es <jigarcia@vaxtor.es>
2020-11-10 08:41:53 -05:00
yhirose f086bf5310 Fix #738 2020-11-08 18:16:23 -05:00
yhirose 6613d7b7ad Fixed warnings 2020-11-08 17:59:11 -05:00
yhirose 6adf130bf3 Fix #739 2020-11-07 21:54:47 -05:00
miketsts b6b2eaf5bc Add unit test SSLConnectTimeout (#741)
Add unit test for issue #682 fixed in PR #728, which does not contain
the test of its own.

The test creates a fake SSL server, inherited from SSLServer, which
does not create an SSL context. When an SSL client attempts to send it
a request, it gets a timeout error. Prior to PR #728, the client would
wait indefinitely

Co-authored-by: Michael Tseitlin <michael.tseitlin@concertio.com>
2020-11-07 09:41:20 -05:00
yhirose eb4b7c70a9 Fix #737 2020-11-07 09:33:22 -05:00
yhirose 84661ea6ed Refactoring 2020-11-05 07:06:53 -05:00
4 changed files with 178 additions and 33 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
run: cd test && make -f Makefile.fuzz_test
- name: setup msbuild on windows
if: matrix.os == 'windows-latest'
uses: warrenbuckley/Setup-MSBuild@v1
uses: microsoft/setup-msbuild@v1.0.2
- name: make-windows
if: matrix.os == 'windows-latest'
run: |
+81 -29
View File
@@ -87,6 +87,14 @@
: 0))
#endif
#ifndef CPPHTTPLIB_RECV_FLAGS
#define CPPHTTPLIB_RECV_FLAGS 0
#endif
#ifndef CPPHTTPLIB_SEND_FLAGS
#define CPPHTTPLIB_SEND_FLAGS 0
#endif
/*
* Headers
*/
@@ -263,7 +271,7 @@ struct ci {
bool operator()(const std::string &s1, const std::string &s2) const {
return std::lexicographical_compare(
s1.begin(), s1.end(), s2.begin(), s2.end(),
[](char c1, char c2) { return ::tolower(c1) < ::tolower(c2); });
[](unsigned char c1, unsigned char c2) { return ::tolower(c1) < ::tolower(c2); });
}
};
@@ -430,7 +438,7 @@ struct Response {
void set_redirect(const char *url, int status = 302);
void set_redirect(const std::string &url, int status = 302);
void set_content(const char *s, size_t n, const char *content_type);
void set_content(std::string s, const char *content_type);
void set_content(const std::string &s, const char *content_type);
void set_content_provider(
size_t length, const char *content_type, ContentProvider provider,
@@ -472,6 +480,7 @@ public:
virtual ssize_t read(char *ptr, size_t size) = 0;
virtual ssize_t write(const char *ptr, size_t size) = 0;
virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
virtual socket_t socket() const = 0;
template <typename... Args>
ssize_t write_format(const char *fmt, const Args &... args);
@@ -681,9 +690,10 @@ private:
bool write_response(Stream &strm, bool close_connection, const Request &req,
Response &res);
bool write_response_with_content(Stream &strm, bool close_connection,
const Request &req, Response &res,
std::string &content_type,
std::string &boundary);
const Request &req, Response &res);
bool write_response_core(Stream &strm, bool close_connection,
const Request &req, Response &res,
bool need_apply_ranges);
bool write_content_with_provider(Stream &strm, const Request &req,
Response &res, const std::string &boundary,
const std::string &content_type);
@@ -1618,6 +1628,10 @@ inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
#else
#ifndef _WIN32
if (sock >= FD_SETSIZE) { return 1; }
#endif
fd_set fds;
FD_ZERO(&fds);
FD_SET(sock, &fds);
@@ -1642,6 +1656,10 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
#else
#ifndef _WIN32
if (sock >= FD_SETSIZE) { return 1; }
#endif
fd_set fds;
FD_ZERO(&fds);
FD_SET(sock, &fds);
@@ -1675,6 +1693,10 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
}
return false;
#else
#ifndef _WIN32
if (sock >= FD_SETSIZE) { return false; }
#endif
fd_set fdsr;
FD_ZERO(&fdsr);
FD_SET(sock, &fdsr);
@@ -1712,6 +1734,7 @@ public:
ssize_t read(char *ptr, size_t size) override;
ssize_t write(const char *ptr, size_t size) override;
void get_remote_ip_and_port(std::string &ip, int &port) const override;
socket_t socket() const override;
private:
socket_t sock_;
@@ -1734,6 +1757,7 @@ public:
ssize_t read(char *ptr, size_t size) override;
ssize_t write(const char *ptr, size_t size) override;
void get_remote_ip_and_port(std::string &ip, int &port) const override;
socket_t socket() const override;
private:
socket_t sock_;
@@ -1755,6 +1779,7 @@ public:
ssize_t read(char *ptr, size_t size) override;
ssize_t write(const char *ptr, size_t size) override;
void get_remote_ip_and_port(std::string &ip, int &port) const override;
socket_t socket() const override;
const std::string &get_buffer() const;
@@ -2970,9 +2995,8 @@ public:
bool is_valid() const { return is_valid_; }
template <typename T, typename U>
bool parse(const char *buf, size_t n, const T &content_callback,
const U &header_callback) {
bool parse(const char *buf, size_t n, const ContentReceiver &content_callback,
const MultipartContentHeader &header_callback) {
static const std::regex re_content_disposition(
"^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename="
@@ -3642,12 +3666,15 @@ inline void Response::set_redirect(const std::string &url, int stat) {
inline void Response::set_content(const char *s, size_t n,
const char *content_type) {
body.assign(s, n);
auto rng = headers.equal_range("Content-Type");
headers.erase(rng.first, rng.second);
set_header("Content-Type", content_type);
}
inline void Response::set_content(std::string s, const char *content_type) {
body = std::move(s);
set_header("Content-Type", content_type);
inline void Response::set_content(const std::string &s,
const char *content_type) {
set_content(s.data(), s.size(), content_type);
}
inline void
@@ -3755,9 +3782,10 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) {
if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
return -1;
}
return recv(sock_, ptr, static_cast<int>(size), 0);
return recv(sock_, ptr, static_cast<int>(size), CPPHTTPLIB_RECV_FLAGS);
#else
return handle_EINTR([&]() { return recv(sock_, ptr, size, 0); });
return handle_EINTR(
[&]() { return recv(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); });
#endif
}
@@ -3768,9 +3796,10 @@ inline ssize_t SocketStream::write(const char *ptr, size_t size) {
if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
return -1;
}
return send(sock_, ptr, static_cast<int>(size), 0);
return send(sock_, ptr, static_cast<int>(size), CPPHTTPLIB_SEND_FLAGS);
#else
return handle_EINTR([&]() { return send(sock_, ptr, size, 0); });
return handle_EINTR(
[&]() { return send(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); });
#endif
}
@@ -3779,6 +3808,8 @@ inline void SocketStream::get_remote_ip_and_port(std::string &ip,
return detail::get_remote_ip_and_port(sock_, ip, port);
}
inline socket_t SocketStream::socket() const { return sock_; }
// Buffer stream implementation
inline bool BufferStream::is_readable() const { return true; }
@@ -3802,6 +3833,8 @@ inline ssize_t BufferStream::write(const char *ptr, size_t size) {
inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
int & /*port*/) const {}
inline socket_t BufferStream::socket() const { return 0; }
inline const std::string &BufferStream::get_buffer() const { return buffer; }
} // namespace detail
@@ -4010,19 +4043,27 @@ inline bool Server::parse_request_line(const char *s, Request &req) {
inline bool Server::write_response(Stream &strm, bool close_connection,
const Request &req, Response &res) {
std::string content_type;
std::string boundary;
return write_response_with_content(strm, close_connection, req, res,
content_type, boundary);
return write_response_core(strm, close_connection, req, res, false);
}
inline bool Server::write_response_with_content(
Stream &strm, bool close_connection, const Request &req, Response &res,
std::string &content_type, std::string &boundary) {
inline bool Server::write_response_with_content(Stream &strm,
bool close_connection,
const Request &req,
Response &res) {
return write_response_core(strm, close_connection, req, res, true);
}
inline bool Server::write_response_core(Stream &strm, bool close_connection,
const Request &req, Response &res,
bool need_apply_ranges) {
assert(res.status != -1);
if (400 <= res.status && error_handler_) { error_handler_(req, res); }
std::string content_type;
std::string boundary;
if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); }
// Headers
if (close_connection || req.get_header_value("Connection") == "close") {
res.set_header("Connection", "close");
@@ -4593,6 +4634,20 @@ Server::process_request(Stream &strm, bool close_connection,
res.version = "HTTP/1.1";
#ifdef _WIN32
// TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL).
#else
#ifndef CPPHTTPLIB_USE_POLL
// Socket file descriptor exceeded FD_SETSIZE...
if (strm.socket() >= FD_SETSIZE) {
Headers dummy;
detail::read_headers(strm, dummy);
res.status = 500;
return write_response(strm, close_connection, req, res);
}
#endif
#endif
// Check if the request URI doesn't exceed the limit
if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
Headers dummy;
@@ -4649,16 +4704,11 @@ Server::process_request(Stream &strm, bool close_connection,
// Rounting
if (routing(req, res, strm)) {
if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
return write_response_with_content(strm, close_connection, req, res);
} else {
if (res.status == -1) { res.status = 404; }
return write_response(strm, close_connection, req, res);
}
std::string content_type;
std::string boundary;
apply_ranges(req, res, content_type, boundary);
return write_response_with_content(strm, close_connection, req, res,
content_type, boundary);
}
inline bool Server::is_valid() const { return true; }
@@ -5848,6 +5898,8 @@ inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
detail::get_remote_ip_and_port(sock_, ip, port);
}
inline socket_t SSLSocketStream::socket() const { return sock_; }
static SSLInit sslinit_;
} // namespace detail
+2
View File
@@ -35,6 +35,8 @@ class FuzzedStream : public httplib::Stream {
port = 8080;
}
socket_t socket() const override { return 0; }
private:
const uint8_t* data_;
size_t size_;
+94 -3
View File
@@ -135,6 +135,17 @@ TEST(GetHeaderValueTest, RegularValue) {
EXPECT_STREQ("text/html", val);
}
TEST(GetHeaderValueTest, SetContent) {
Response res;
res.set_content("html", "text/html");
EXPECT_EQ("text/html", res.get_header_value("Content-Type"));
res.set_content("text", "text/plain");
EXPECT_EQ(1, res.get_header_value_count("Content-Type"));
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
}
TEST(GetHeaderValueTest, RegularValueInt) {
Headers headers = {{"Content-Length", "100"}, {"Dummy", "Dummy"}};
auto val =
@@ -830,7 +841,7 @@ TEST(UrlWithSpace, Redirect) {
}
#endif
TEST(Server, BindDualStack) {
TEST(BindServerTest, BindDualStack) {
Server svr;
svr.Get("/1", [&](const Request & /*req*/, Response &res) {
@@ -863,7 +874,7 @@ TEST(Server, BindDualStack) {
ASSERT_FALSE(svr.is_running());
}
TEST(Server, BindAndListenSeparately) {
TEST(BindServerTest, BindAndListenSeparately) {
Server svr;
int port = svr.bind_to_any_port("0.0.0.0");
ASSERT_TRUE(svr.is_valid());
@@ -872,7 +883,7 @@ TEST(Server, BindAndListenSeparately) {
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(SSLServer, BindAndListenSeparately) {
TEST(BindServerTest, BindAndListenSeparatelySSL) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
CLIENT_CA_CERT_DIR);
int port = svr.bind_to_any_port("0.0.0.0");
@@ -882,6 +893,41 @@ TEST(SSLServer, BindAndListenSeparately) {
}
#endif
TEST(ErrorHandlerTest, ContentLength) {
Server svr;
svr.set_error_handler([](const Request & /*req*/, Response &res) {
res.status = 200;
res.set_content("abcdefghijklmnopqrstuvwxyz",
"text/html"); // <= Content-Length still 13
});
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
res.set_content("Hello World!\n", "text/plain");
res.status = 524;
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
{
Client cli(HOST, PORT);
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
EXPECT_EQ(200, 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();
thread.join();
ASSERT_FALSE(svr.is_running());
}
class ServerTest : public ::testing::Test {
protected:
ServerTest()
@@ -3459,6 +3505,51 @@ TEST(SSLClientServerTest, TrustDirOptional) {
t.join();
}
TEST(SSLClientServerTest, SSLConnectTimeout) {
class NoListenSSLServer : public SSLServer {
public:
NoListenSSLServer(const char *cert_path, const char *private_key_path,
const char *client_ca_cert_file_path,
const char *client_ca_cert_dir_path = nullptr)
: SSLServer(cert_path, private_key_path, client_ca_cert_file_path,
client_ca_cert_dir_path),
stop_(false) {}
bool stop_;
private:
bool process_and_close_socket(socket_t /*sock*/) override {
// Don't create SSL context
while (!stop_) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return true;
}
};
NoListenSSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE,
CLIENT_CA_CERT_FILE);
ASSERT_TRUE(svr.is_valid());
svr.Get("/test", [&](const Request &, Response &res) {
res.set_content("test", "text/plain");
});
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
std::this_thread::sleep_for(std::chrono::milliseconds(1));
SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
cli.enable_server_certificate_verification(false);
cli.set_connection_timeout(1);
auto res = cli.Get("/test");
ASSERT_TRUE(!res);
EXPECT_EQ(Error::SSLConnection, res.error());
svr.stop_ = true;
svr.stop();
t.join();
}
#endif
#ifdef _WIN32