Skip to content

Commit 7196ac8

Browse files
committed
Fix incorrect handling of Expect: 100-continue
Fix #1808
1 parent b1f8e98 commit 7196ac8

File tree

3 files changed

+106
-2
lines changed

3 files changed

+106
-2
lines changed

httplib.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6956,7 +6956,9 @@ Server::process_request(Stream &strm, bool close_connection,
69566956
strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
69576957
status_message(status));
69586958
break;
6959-
default: return write_response(strm, close_connection, req, res);
6959+
default:
6960+
connection_closed = true;
6961+
return write_response(strm, true, req, res);
69606962
}
69616963
}
69626964

test/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ else()
2424
FetchContent_MakeAvailable(gtest)
2525
endif()
2626

27+
find_package(curl REQUIRED)
28+
2729
add_executable(httplib-test test.cc)
2830
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8;/bigobj>")
29-
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main)
31+
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main CURL::libcurl)
3032
gtest_discover_tests(httplib-test)
3133

3234
file(

test/test.cc

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <httplib.h>
22
#include <signal.h>
33

4+
#include <curl/curl.h>
45
#include <gtest/gtest.h>
56

67
#include <atomic>
@@ -12,6 +13,7 @@
1213
#include <stdexcept>
1314
#include <thread>
1415
#include <type_traits>
16+
#include <vector>
1517

1618
#define SERVER_CERT_FILE "./cert.pem"
1719
#define SERVER_CERT2_FILE "./cert2.pem"
@@ -7606,3 +7608,101 @@ TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
76067608
Client cli(HOST, PORT);
76077609
cli.Get("/test", {{"Test", "_\n\r_\n\r_"}});
76087610
}
7611+
7612+
TEST(Expect100ContinueTest, ServerClosesConnection) {
7613+
static constexpr char reject[] = "Unauthorized";
7614+
static constexpr char accept[] = "Upload accepted";
7615+
constexpr size_t total_size = 10 * 1024 * 1024 * 1024ULL;
7616+
7617+
Server svr;
7618+
7619+
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
7620+
res.status = StatusCode::Unauthorized_401;
7621+
res.set_content(reject, "text/plain");
7622+
return res.status;
7623+
});
7624+
svr.Post("/", [&](const Request & /*req*/, Response &res) {
7625+
res.set_content(accept, "text/plain");
7626+
});
7627+
7628+
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
7629+
auto se = detail::scope_exit([&] {
7630+
svr.stop();
7631+
thread.join();
7632+
ASSERT_FALSE(svr.is_running());
7633+
});
7634+
7635+
svr.wait_until_ready();
7636+
7637+
{
7638+
const auto curl = std::unique_ptr<CURL, decltype(&curl_easy_cleanup)>{
7639+
curl_easy_init(), &curl_easy_cleanup};
7640+
ASSERT_NE(curl, nullptr);
7641+
7642+
curl_easy_setopt(curl.get(), CURLOPT_URL, HOST);
7643+
curl_easy_setopt(curl.get(), CURLOPT_PORT, PORT);
7644+
curl_easy_setopt(curl.get(), CURLOPT_POST, 1L);
7645+
auto list = std::unique_ptr<curl_slist, decltype(&curl_slist_free_all)>{
7646+
curl_slist_append(nullptr, "Content-Type: application/octet-stream"),
7647+
&curl_slist_free_all};
7648+
ASSERT_NE(list, nullptr);
7649+
curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, list.get());
7650+
7651+
struct read_data {
7652+
size_t read_size;
7653+
size_t total_size;
7654+
} data = {0, total_size};
7655+
using read_callback_t =
7656+
size_t (*)(char *ptr, size_t size, size_t nmemb, void *userdata);
7657+
read_callback_t read_callback = [](char *ptr, size_t size, size_t nmemb,
7658+
void *userdata) -> size_t {
7659+
read_data *data = (read_data *)userdata;
7660+
7661+
if (!userdata || data->read_size >= data->total_size) { return 0; }
7662+
7663+
std::fill_n(ptr, size * nmemb, 'A');
7664+
data->read_size += size * nmemb;
7665+
return size * nmemb;
7666+
};
7667+
curl_easy_setopt(curl.get(), CURLOPT_READDATA, data);
7668+
curl_easy_setopt(curl.get(), CURLOPT_READFUNCTION, read_callback);
7669+
7670+
std::vector<char> buffer;
7671+
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &buffer);
7672+
using write_callback_t =
7673+
size_t (*)(char *ptr, size_t size, size_t nmemb, void *userdata);
7674+
write_callback_t write_callback = [](char *ptr, size_t size, size_t nmemb,
7675+
void *userdata) -> size_t {
7676+
std::vector<char> *buffer = (std::vector<char> *)userdata;
7677+
buffer->reserve(buffer->size() + size * nmemb + 1);
7678+
buffer->insert(buffer->end(), (char *)ptr, (char *)ptr + size * nmemb);
7679+
return size * nmemb;
7680+
};
7681+
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_callback);
7682+
7683+
{
7684+
const auto res = curl_easy_perform(curl.get());
7685+
ASSERT_EQ(res, CURLE_OK);
7686+
}
7687+
7688+
{
7689+
auto response_code = long{};
7690+
const auto res =
7691+
curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &response_code);
7692+
ASSERT_EQ(res, CURLE_OK);
7693+
ASSERT_EQ(response_code, StatusCode::Unauthorized_401);
7694+
}
7695+
7696+
{
7697+
auto dl = curl_off_t{};
7698+
const auto res = curl_easy_getinfo(curl.get(), CURLINFO_SIZE_DOWNLOAD_T, &dl);
7699+
ASSERT_EQ(res, CURLE_OK);
7700+
ASSERT_EQ(dl, sizeof reject - 1);
7701+
}
7702+
7703+
{
7704+
buffer.push_back('\0');
7705+
ASSERT_STRCASEEQ(buffer.data(), reject);
7706+
}
7707+
}
7708+
}

0 commit comments

Comments
 (0)