Compare commits

...

36 Commits

Author SHA1 Message Date
yhirose f14accb7b6 Release v0.14.2 2023-12-04 22:31:12 -05:00
yhirose c5c704cb3b Fix #1724 2023-12-04 21:34:55 -05:00
Jean-Francois Simoneau 115a786581 Fix readability warnings (#1722)
* Fix readability warnings

Did not fix readbility-qualified-auto, will do a separate pull request

* Revert changes where meaning is lost

* Revert some style changes
2023-11-24 09:55:04 -05:00
Jean-Francois Simoneau 5ef4cfd263 Fix bugprone warnings (#1721) 2023-11-20 22:14:00 -05:00
Jean-Francois Simoneau 03fecb2f78 Fix modernize warnings (#1720) 2023-11-20 22:10:04 -05:00
Jean-Francois Simoneau 7fc8682a0a Fix performance-noexcept-move-constructor (#1715) 2023-11-20 13:13:59 -05:00
Jiwoo Park f1431311a4 Minor fixes on test cases (#1709)
* Fix data race

* Replace sleep_for() to wait_until_ready()
2023-11-11 21:28:50 -05:00
Jiwoo Park 1d14e051a5 Remove cryptui on Windows (#1710) 2023-11-11 21:26:57 -05:00
Jiwoo Park 97ae6733ed Run fuzz test in CTest (#1707) 2023-11-09 19:35:15 -05:00
Jiwoo Park 1d6b22b5f0 Fix C6001 (#1701) 2023-10-30 07:13:40 -04:00
yhirose 1a49076b5b Removed unnecessary exception 2023-10-29 19:36:40 -04:00
Jiwoo Park d0e4cb3f07 Include missing stdint.h on fuzz test (#1700)
* Include missing stdint.h

* Remove std:: from uint8_t
2023-10-29 19:26:06 -04:00
yhirose e2813d9d4d Code cleanup. (Removed unnecessary .c_str() calls) 2023-10-23 16:43:12 -04:00
Andrea Pappacoda 20a7f088ce build(meson): copy 1MB.txt test file (#1695)
Since tests are run in the build directory, the 1MB.txt file has to be
copied there.
2023-10-20 17:58:06 -04:00
yhirose f63ba7d013 Fix #1685 2023-10-03 09:59:27 -04:00
yhirose 0a629d7391 Release v0.14.1 2023-09-30 22:26:23 -04:00
PabloMK7 a609330e4c Add optional user defined header writer (#1683)
* Add optional user defined header writer

* Fix errors and add test
2023-09-30 22:13:14 -04:00
Jiwoo Park c029597a5a Update the remote address of www.httpwatch.com (#1664) 2023-09-13 10:33:33 -04:00
yhirose 30b7732565 Release v0.14.0 2023-08-22 20:19:07 -04:00
yhirose 6650632e7f Fix #1638 2023-08-22 19:36:10 -04:00
Sven Panne afe627e7af Avoid a -Warray-bounds false positive in GCC 13. (#1639)
The exact circumstances when this false positive is triggered are quite
tricky to reproduce, but it happened reproducibly with g++ 13.1 and 13.2 in
a close-source SW I'm working on.  The fix even improves performance by a
very tiny bit: There is no need to copy the std::smatch, having a const
reference is enough.

Just as a side note: -Warray-bounds seems to cause trouble in other
projects, too, so e.g. the Linux kernel has disabled since June 2022.
2023-08-14 10:26:54 -04:00
Duncan Ogilvie 67f6ff7fa9 Fix CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR (#1634) 2023-08-03 17:01:40 -04:00
yhirose c7ed1796a7 Release v0.13.3 2023-07-31 21:28:33 -04:00
yhirose 44c62d838e Use memory mapped file for static file server (#1632)
* Use memory mapped file for static file server

* Fix build error
2023-07-31 07:43:50 -04:00
yhirose 6bb580cda8 Fix #1559 2023-07-31 00:27:26 -04:00
yhirose 00a8cb8e5d Code cleanup 2023-07-31 00:22:22 -04:00
yhirose 2e34a39673 Added StaticFileRanges test 2023-07-31 00:22:22 -04:00
yhirose 01b90829bc Removed unnecessary CRLF at the end of multipart ranges data 2023-07-31 00:22:22 -04:00
yhirose e699bd0730 Release v0.13.2 2023-07-29 12:26:19 -04:00
Akın ELDEN 961a9379d5 Fix #1590 (#1630)
* ClientImpl: Connection=close header control moved from process_request to send_

* Connection=close header control moved from send_ to handle_request

* SSLClient::connect_with_proxy error handling improved

* to_string definition added for Error::ProxyConnection

* Comment improvement

---------

Co-authored-by: akinelden <akin.elden@gmail.com>
2023-07-29 12:09:25 -04:00
yhirose ec87b04aff Fix #1619 2023-07-29 00:53:57 -04:00
yhirose aabf752a51 Fix #1519 2023-07-28 23:37:45 -04:00
yhirose afb0674ccb Fix #1624 2023-07-21 20:36:01 -04:00
bdenhollander ee625232a4 Fix successful decompress reported as Error::Read (#1612)
* Fix successful decompress reported as Error::Read

Streams less than 4096 bytes are sometimes reported as failed reads because stream_.avail_in is not reduced to 0. The next iteration of the loop finds `prev_avail_in == strm_.avail_in` and return false. `ret = inflate(...)` returns Z_STREAM_END on the first iteration of the loop indicating that inflate is finished. This fix prevents the second iteration of the loop from failing.

* Fix successful decompress reported as Error::Read

- Add unit tests for raw deflate that illustrates the decompression failure when there are extra trailing bytes
2023-07-11 18:35:27 -04:00
Andrea Pappacoda 52d8dd41f1 build(meson): use C++14 with GTest >= 1.13.0 (#1618)
GoogleTest, starting with vesion 1.13.0, requires C++14 to build. This
patch enables C++14 if GoogleTest 1.13.0 or newer is detected when
compiling the tests with Meson, making it possible to use new GTest
versions.

You can find GoogleTest's release notes at
<https://github.com/google/googletest/releases/tag/v1.13.0>.
2023-07-11 18:32:41 -04:00
Alexandre Bouvier be07d2d7a9 cmake: fix comment (#1616) 2023-07-08 13:24:36 -04:00
10 changed files with 9044 additions and 379 deletions
+2 -3
View File
@@ -219,7 +219,6 @@ target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
# Needed for Windows libs on Mingw, as the pragma comment(lib, "xyz") aren't triggered.
$<$<PLATFORM_ID:Windows>:ws2_32>
$<$<PLATFORM_ID:Windows>:crypt32>
$<$<PLATFORM_ID:Windows>:cryptui>
# Needed for API from MacOS Security framework
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN}>>:-framework CoreFoundation -framework Security>"
# Can't put multiple targets in a single generator expression or it bugs out.
@@ -256,13 +255,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 :)
+489 -269
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -102,3 +102,5 @@ if(HTTPLIB_IS_USING_OPENSSL)
COMMAND_ERROR_IS_FATAL ANY
)
endif()
add_subdirectory(fuzzing)
+10
View File
@@ -0,0 +1,10 @@
file(GLOB HTTPLIB_CORPUS corpus/*)
add_executable(httplib-test-fuzz
server_fuzzer.cc
standalone_fuzz_target_runner.cpp
)
target_link_libraries(httplib-test-fuzz PRIVATE httplib)
add_test(
NAME httplib-test-fuzz
COMMAND httplib-test-fuzz ${HTTPLIB_CORPUS}
)
+2 -1
View File
@@ -1,5 +1,6 @@
#include <cstdint>
#include <httplib.h>
#include <memory>
class FuzzedStream : public httplib::Stream {
public:
@@ -5,9 +5,9 @@
// on the test corpus or on a single file,
// e.g. the one that comes from a bug report.
#include <cassert>
#include <iostream>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <vector>
// Forward declare the "fuzz target" interface.
@@ -21,7 +21,7 @@ int main(int argc, char **argv) {
std::ifstream in(argv[i]);
in.seekg(0, in.end);
size_t length = static_cast<size_t>(in.tellg());
in.seekg (0, in.beg);
in.seekg(0, in.beg);
std::cout << "Reading " << length << " bytes from " << argv[i] << std::endl;
// Allocate exactly length bytes so that we reliably catch buffer overflows.
std::vector<char> bytes(length);
+8 -1
View File
@@ -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,
+335 -102
View File
@@ -211,8 +211,7 @@ TEST(GetHeaderValueTest, DefaultValue) {
TEST(GetHeaderValueTest, DefaultValueInt) {
Headers headers = {{"Dummy", "Dummy"}};
auto val =
detail::get_header_value<uint64_t>(headers, "Content-Length", 0, 100);
auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 100);
EXPECT_EQ(100ull, val);
}
@@ -241,8 +240,7 @@ TEST(GetHeaderValueTest, SetContent) {
TEST(GetHeaderValueTest, RegularValueInt) {
Headers headers = {{"Content-Length", "100"}, {"Dummy", "Dummy"}};
auto val =
detail::get_header_value<uint64_t>(headers, "Content-Length", 0, 0);
auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 0);
EXPECT_EQ(100ull, val);
}
@@ -444,7 +442,7 @@ TEST(HostnameToIPConversionTest, HTTPWatch_Online) {
auto host = "www.httpwatch.com";
auto ip = hosted_at(host);
EXPECT_EQ("191.236.16.12", ip);
EXPECT_EQ("23.96.13.243", ip);
std::vector<std::string> addrs;
hosted_at(host, addrs);
@@ -1016,7 +1014,7 @@ TEST(UrlWithSpace, Redirect_Online) {
auto res = cli.Get("/files/2595/310/Neat 1.4-17.jar");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ(18527U, res->get_header_value<uint64_t>("Content-Length"));
EXPECT_EQ(18527U, res->get_header_value_u64("Content-Length"));
}
#endif
@@ -1090,12 +1088,8 @@ TEST(RedirectToDifferentPort, Redirect) {
ASSERT_FALSE(svr1.is_running());
});
while (!svr1.is_running() || !svr2.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
svr1.wait_until_ready();
svr2.wait_until_ready();
Client cli("localhost", svr2_port);
cli.set_follow_location(true);
@@ -1127,9 +1121,6 @@ TEST(RedirectFromPageWithContent, Redirect) {
svr.wait_until_ready();
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
{
Client cli("localhost", PORT);
cli.set_follow_location(true);
@@ -1195,9 +1186,6 @@ TEST(RedirectFromPageWithContentIP6, Redirect) {
ASSERT_LT(milliseconds, 5000U);
}
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
{
Client cli("http://[::1]:1234");
cli.set_follow_location(true);
@@ -1248,8 +1236,7 @@ TEST(PathUrlEncodeTest, PathUrlEncode) {
ASSERT_FALSE(svr.is_running());
});
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
svr.wait_until_ready();
{
Client cli(HOST, PORT);
@@ -1279,8 +1266,7 @@ TEST(BindServerTest, DISABLED_BindDualStack) {
ASSERT_FALSE(svr.is_running());
});
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
svr.wait_until_ready();
{
Client cli("127.0.0.1", PORT);
@@ -1351,8 +1337,7 @@ TEST(ErrorHandlerTest, ContentLength) {
ASSERT_FALSE(svr.is_running());
});
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
svr.wait_until_ready();
{
Client cli(HOST, PORT);
@@ -1393,8 +1378,7 @@ TEST(ExceptionHandlerTest, ContentLength) {
ASSERT_FALSE(svr.is_running());
});
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
svr.wait_until_ready();
for (size_t i = 0; i < 10; i++) {
Client cli(HOST, PORT);
@@ -1434,8 +1418,7 @@ TEST(NoContentTest, ContentLength) {
ASSERT_FALSE(svr.is_running());
});
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
svr.wait_until_ready();
{
Client cli(HOST, PORT);
@@ -1485,8 +1468,7 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
ASSERT_FALSE(svr.is_running());
});
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
svr.wait_until_ready();
{
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -1554,8 +1536,7 @@ TEST(InvalidFormatTest, StatusCode) {
ASSERT_FALSE(svr.is_running());
});
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
svr.wait_until_ready();
{
Client cli(HOST, PORT);
@@ -1579,7 +1560,7 @@ TEST(URLFragmentTest, WithFragment) {
ASSERT_FALSE(svr.is_running());
});
std::this_thread::sleep_for(std::chrono::seconds(1));
svr.wait_until_ready();
{
Client cli(HOST, PORT);
@@ -1594,6 +1575,46 @@ TEST(URLFragmentTest, WithFragment) {
}
}
TEST(HeaderWriter, SetHeaderWriter) {
Server svr;
svr.set_header_writer([](Stream &strm, Headers &hdrs) {
hdrs.emplace("CustomServerHeader", "CustomServerValue");
return detail::write_headers(strm, hdrs);
});
svr.Get("/hi", [](const Request &req, Response &res) {
auto it = req.headers.find("CustomClientHeader");
EXPECT_TRUE(it != req.headers.end());
EXPECT_EQ(it->second, "CustomClientValue");
res.set_content("Hello World!\n", "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_header_writer([](Stream &strm, Headers &hdrs) {
hdrs.emplace("CustomClientHeader", "CustomClientValue");
return detail::write_headers(strm, hdrs);
});
auto res = cli.Get("/hi");
EXPECT_TRUE(res);
EXPECT_EQ(200, res->status);
auto it = res->headers.find("CustomServerHeader");
EXPECT_TRUE(it != res->headers.end());
EXPECT_EQ(it->second, "CustomServerValue");
}
}
class ServerTest : public ::testing::Test {
protected:
ServerTest()
@@ -2167,9 +2188,7 @@ protected:
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(HOST, PORT)); });
while (!svr_.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
svr_.wait_until_ready();
}
virtual void TearDown() {
@@ -2501,6 +2520,55 @@ TEST_F(ServerTest, StaticFileRange) {
EXPECT_EQ(std::string("cd"), res->body);
}
TEST_F(ServerTest, StaticFileRanges) {
auto res =
cli_.Get("/dir/test.abcde", {{make_range_header({{1, 2}, {4, -1}})}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_TRUE(
res->get_header_value("Content-Type")
.find(
"multipart/byteranges; boundary=--cpp-httplib-multipart-data-") ==
0);
EXPECT_EQ("265", res->get_header_value("Content-Length"));
}
TEST_F(ServerTest, StaticFileRangeHead) {
auto res = cli_.Head("/dir/test.abcde", {{make_range_header({{2, 3}})}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_EQ("text/abcde", res->get_header_value("Content-Type"));
EXPECT_EQ("2", res->get_header_value("Content-Length"));
EXPECT_EQ(true, res->has_header("Content-Range"));
}
TEST_F(ServerTest, StaticFileRangeBigFile) {
auto res = cli_.Get("/dir/1MB.txt", {{make_range_header({{-1, 5}})}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ("5", res->get_header_value("Content-Length"));
EXPECT_EQ(true, res->has_header("Content-Range"));
EXPECT_EQ("LAST\n", res->body);
}
TEST_F(ServerTest, StaticFileRangeBigFile2) {
auto res = cli_.Get("/dir/1MB.txt", {{make_range_header({{1, 4097}})}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ("4097", res->get_header_value("Content-Length"));
EXPECT_EQ(true, res->has_header("Content-Range"));
}
TEST_F(ServerTest, StaticFileBigFile) {
auto res = cli_.Get("/dir/1MB.txt");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ("1048576", res->get_header_value("Content-Length"));
}
TEST_F(ServerTest, InvalidBaseDirMount) {
EXPECT_EQ(false, svr_.set_mount_point("invalid_mount_point", "./www3"));
}
@@ -2886,9 +2954,9 @@ TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
cli_.Get("/streamed-with-range", {{make_range_header({{1, 2}, {4, 5}})}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_EQ("269", res->get_header_value("Content-Length"));
EXPECT_EQ("267", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(269U, res->body.size());
EXPECT_EQ(267U, res->body.size());
}
TEST_F(ServerTest, GetStreamedEndless) {
@@ -2978,9 +3046,9 @@ TEST_F(ServerTest, GetWithRangeMultipart) {
auto res = cli_.Get("/with-range", {{make_range_header({{1, 2}, {4, 5}})}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_EQ("269", res->get_header_value("Content-Length"));
EXPECT_EQ("267", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(269U, res->body.size());
EXPECT_EQ(267U, res->body.size());
}
TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
@@ -3247,7 +3315,7 @@ TEST_F(ServerTest, PutLargeFileWithGzip2) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ(LARGE_DATA, res->body);
EXPECT_EQ(101942u, res.get_request_header_value<uint64_t>("Content-Length"));
EXPECT_EQ(101942u, res.get_request_header_value_u64("Content-Length"));
EXPECT_EQ("gzip", res.get_request_header_value("Content-Encoding"));
}
@@ -3330,6 +3398,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) {
@@ -4082,9 +4203,6 @@ TEST(MountTest, Unmount) {
svr.wait_until_ready();
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", PORT);
svr.set_mount_point("/mount2", "./www2");
@@ -4135,9 +4253,6 @@ TEST(ExceptionTest, ThrowExceptionInHandler) {
svr.wait_until_ready();
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", PORT);
{
@@ -4179,9 +4294,6 @@ TEST(KeepAliveTest, ReadTimeout) {
svr.wait_until_ready();
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", PORT);
cli.set_keep_alive(true);
cli.set_read_timeout(std::chrono::seconds(1));
@@ -4211,7 +4323,7 @@ TEST(KeepAliveTest, Issue1041) {
ASSERT_FALSE(svr.is_running());
});
std::this_thread::sleep_for(std::chrono::milliseconds(200));
svr.wait_until_ready();
Client cli(HOST, PORT);
cli.set_keep_alive(true);
@@ -4244,7 +4356,7 @@ TEST(KeepAliveTest, SSLClientReconnection) {
ASSERT_FALSE(svr.is_running());
});
std::this_thread::sleep_for(std::chrono::milliseconds(200));
svr.wait_until_ready();
SSLClient cli(HOST, PORT);
cli.enable_server_certificate_verification(false);
@@ -4307,9 +4419,6 @@ TEST(ClientProblemDetectionTest, ContentProvider) {
svr.wait_until_ready();
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", PORT);
{
@@ -4347,9 +4456,6 @@ TEST(ErrorHandlerWithContentProviderTest, ErrorHandler) {
svr.wait_until_ready();
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", PORT);
auto res = cli.Get("/");
@@ -4358,6 +4464,43 @@ TEST(ErrorHandlerWithContentProviderTest, ErrorHandler) {
EXPECT_EQ("helloworld", res->body);
}
TEST(LongPollingTest, ClientCloseDetection) {
Server svr;
svr.Get("/events", [&](const Request & /*req*/, Response &res) {
res.set_chunked_content_provider(
"text/plain", [](std::size_t const, DataSink &sink) -> bool {
EXPECT_TRUE(sink.is_writable()); // the socket is alive
sink.os << "hello";
auto count = 10;
while (count > 0 && sink.is_writable()) {
this_thread::sleep_for(chrono::milliseconds(10));
}
EXPECT_FALSE(sink.is_writable()); // the socket is closed
return true;
});
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli("localhost", PORT);
auto res = cli.Get("/events", [&](const char *data, size_t data_length) {
EXPECT_EQ("hello", string(data, data_length));
return false; // close the socket immediately.
});
ASSERT_FALSE(res);
}
TEST(GetWithParametersTest, GetWithParameters) {
Server svr;
@@ -4393,7 +4536,6 @@ TEST(GetWithParametersTest, GetWithParameters) {
});
svr.wait_until_ready();
std::this_thread::sleep_for(std::chrono::seconds(1));
{
Client cli(HOST, PORT);
@@ -4452,7 +4594,6 @@ TEST(GetWithParametersTest, GetWithParameters2) {
});
svr.wait_until_ready();
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", PORT);
@@ -4520,7 +4661,6 @@ TEST(ServerDefaultHeadersTest, DefaultHeaders) {
});
svr.wait_until_ready();
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", PORT);
@@ -4555,9 +4695,6 @@ TEST(KeepAliveTest, ReadTimeoutSSL) {
svr.wait_until_ready();
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
SSLClient cli("localhost", PORT);
cli.enable_server_certificate_verification(false);
cli.set_keep_alive(true);
@@ -4596,9 +4733,7 @@ protected:
t_ = thread(
[&]() { ASSERT_TRUE(svr_.listen(std::string(), PORT, AI_PASSIVE)); });
while (!svr_.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
svr_.wait_until_ready();
}
virtual void TearDown() {
@@ -4635,9 +4770,7 @@ protected:
ASSERT_TRUE(svr_.listen_after_bind());
});
while (!svr_.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
svr_.wait_until_ready();
}
virtual void TearDown() {
@@ -4678,9 +4811,7 @@ protected:
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(HOST, PORT)); });
while (!svr_.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
svr_.wait_until_ready();
}
virtual void TearDown() {
@@ -4797,7 +4928,7 @@ TEST(SSLClientTest, ServerCertificateVerification4) {
ASSERT_FALSE(svr.is_running());
});
std::this_thread::sleep_for(std::chrono::milliseconds(1));
svr.wait_until_ready();
SSLClient cli("127.0.0.1", PORT);
cli.set_ca_cert_path(SERVER_CERT2_FILE);
@@ -4913,7 +5044,7 @@ TEST(SSLClientServerTest, ClientCertPresent) {
ASSERT_FALSE(svr.is_running());
});
std::this_thread::sleep_for(std::chrono::milliseconds(1));
svr.wait_until_ready();
SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
cli.enable_server_certificate_verification(false);
@@ -4988,7 +5119,7 @@ TEST(SSLClientServerTest, MemoryClientCertPresent) {
ASSERT_FALSE(svr.is_running());
});
std::this_thread::sleep_for(std::chrono::milliseconds(1));
svr.wait_until_ready();
SSLClient cli(HOST, PORT, client_cert, client_private_key);
cli.enable_server_certificate_verification(false);
@@ -5019,7 +5150,7 @@ TEST(SSLClientServerTest, ClientCertMissing) {
ASSERT_FALSE(svr.is_running());
});
std::this_thread::sleep_for(std::chrono::milliseconds(1));
svr.wait_until_ready();
SSLClient cli(HOST, PORT);
auto res = cli.Get("/test");
@@ -5043,7 +5174,7 @@ TEST(SSLClientServerTest, TrustDirOptional) {
ASSERT_FALSE(svr.is_running());
});
std::this_thread::sleep_for(std::chrono::milliseconds(1));
svr.wait_until_ready();
SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
cli.enable_server_certificate_verification(false);
@@ -5064,12 +5195,12 @@ TEST(SSLClientServerTest, SSLConnectTimeout) {
client_ca_cert_dir_path),
stop_(false) {}
bool stop_;
std::atomic_bool stop_;
private:
bool process_and_close_socket(socket_t /*sock*/) override {
// Don't create SSL context
while (!stop_) {
while (!stop_.load()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return true;
@@ -5091,7 +5222,7 @@ TEST(SSLClientServerTest, SSLConnectTimeout) {
ASSERT_FALSE(svr.is_running());
});
std::this_thread::sleep_for(std::chrono::milliseconds(1));
svr.wait_until_ready();
SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
cli.enable_server_certificate_verification(false);
@@ -5163,7 +5294,7 @@ TEST(SSLClientServerTest, CustomizeServerSSLCtx) {
ASSERT_FALSE(svr.is_running());
});
std::this_thread::sleep_for(std::chrono::milliseconds(1));
svr.wait_until_ready();
SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
cli.enable_server_certificate_verification(false);
@@ -5299,9 +5430,10 @@ TEST(ServerLargeContentTest, DISABLED_SendLargeContent) {
ASSERT_TRUE(content);
Server svr;
svr.Get("/foo", [=](const httplib::Request &req, httplib::Response &resp) {
resp.set_content(content, content_size, "application/octet-stream");
});
svr.Get("/foo",
[=](const httplib::Request & /*req*/, httplib::Response &res) {
res.set_content(content, content_size, "application/octet-stream");
});
auto listen_thread = std::thread([&svr]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
@@ -5313,9 +5445,6 @@ TEST(ServerLargeContentTest, DISABLED_SendLargeContent) {
svr.wait_until_ready();
// 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("/foo");
ASSERT_TRUE(res);
@@ -5450,7 +5579,8 @@ TEST(HttpToHttpsRedirectTest, CertFile) {
ASSERT_FALSE(svr.is_running());
});
std::this_thread::sleep_for(std::chrono::milliseconds(1));
svr.wait_until_ready();
ssl_svr.wait_until_ready();
Client cli("127.0.0.1", PORT);
cli.set_ca_cert_path(SERVER_CERT2_FILE);
@@ -5506,7 +5636,6 @@ TEST(MultipartFormDataTest, LargeData) {
});
svr.wait_until_ready();
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::string data(1024 * 1024 * 2, '.');
@@ -5654,7 +5783,6 @@ TEST(MultipartFormDataTest, DataProviderItems) {
});
svr.wait_until_ready();
std::this_thread::sleep_for(std::chrono::seconds(1));
{
Client cli("https://localhost:8080");
@@ -5856,7 +5984,6 @@ TEST(MultipartFormDataTest, PostCustomBoundary) {
});
svr.wait_until_ready();
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::string data(1024 * 1024 * 2, '.');
@@ -5878,9 +6005,6 @@ TEST(MultipartFormDataTest, PostCustomBoundary) {
}
TEST(MultipartFormDataTest, PostInvalidBoundaryChars) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::string data(1024 * 1024 * 2, '&');
std::stringstream buffer;
buffer << data;
@@ -5943,7 +6067,6 @@ TEST(MultipartFormDataTest, PutFormData) {
});
svr.wait_until_ready();
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::string data(1024 * 1024 * 2, '&');
@@ -6008,7 +6131,6 @@ TEST(MultipartFormDataTest, PutFormDataCustomBoundary) {
});
svr.wait_until_ready();
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::string data(1024 * 1024 * 2, '&');
@@ -6030,9 +6152,6 @@ TEST(MultipartFormDataTest, PutFormDataCustomBoundary) {
}
TEST(MultipartFormDataTest, PutInvalidBoundaryChars) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::string data(1024 * 1024 * 2, '&');
std::stringstream buffer;
buffer << data;
@@ -6052,6 +6171,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
@@ -6247,7 +6480,7 @@ TEST(RedirectTest, RedirectToUrlWithQueryParameters) {
ASSERT_FALSE(svr.is_running());
});
std::this_thread::sleep_for(std::chrono::seconds(1));
svr.wait_until_ready();
{
Client cli(HOST, PORT);
@@ -6293,7 +6526,7 @@ TEST(VulnerabilityTest, CRLFInjection) {
ASSERT_FALSE(svr.is_running());
});
std::this_thread::sleep_for(std::chrono::seconds(1));
svr.wait_until_ready();
{
Client cli(HOST, PORT);
+8192
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -5,3 +5,4 @@
configure_file(input: 'index.html', output: 'index.html', copy: true)
configure_file(input: 'test.abcde', output: 'test.abcde', copy: true)
configure_file(input: 'test.html', output: 'test.html', copy: true)
configure_file(input: '1MB.txt', output: '1MB.txt', copy: true)