Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions httplib.h
Original file line number Diff line number Diff line change
Expand Up @@ -7881,6 +7881,65 @@ inline bool Server::handle_file_request(const Request &req, Response &res) {
return false;
}

#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
/*
* The HTTP request header If-None-Match can be used with other
* methods where it has the meaning to only execute if the resource
* does not already exist but uploading does not matter here.
* HTTP response header ETag is only set where content is
* pulled and not pushed as those HTTP response bodies do not have to
* be related to the content.
*/
if (req.method == "GET" || req.method == "HEAD") {
// Value for HTTP response header ETag.
const std::string file_data(mm->data(), mm->size());
const std::string etag =
R"(")" + detail::SHA_512(file_data) + R"(")";
/*
* Weak validation is not used.
* HTTP response header ETag must be set as if normal HTTP response
* was sent.
*/
res.set_header("ETag", etag);

/*
* Semantic: If value exists, the server will send status code 304.
* * always results in status code 304.
*/
if (req.has_header("If-None-Match")) {
const std::string header_if_none_match =
req.get_header_value("If-None-Match");

/*
* Values of HTTP request header If-None-Match which are cached
* values of previous HTTP response header ETag.
*/
std::set<std::string> etags;
detail::split(header_if_none_match.c_str(),
header_if_none_match.c_str() +
header_if_none_match.length(),
',', [&](const char *b, const char *e) {
std::string etag(b, e);

// Weak validation is not used.
if (etag.length() >= 2 && etag.at(0) == 'W' &&
etag.at(1) == '/') {
etag.erase(0, 2);
}

etags.insert(std::move(etag));
});

if (etags.find("*") != etags.cend() ||
etags.find(etag) != etags.cend()) {
res.status = StatusCode::NotModified_304;
res.body.clear();
return true;
}
}
}
#endif

res.set_content_provider(
mm->size(),
detail::find_content_type(path, file_extension_and_mimetype_map_,
Expand Down
37 changes: 37 additions & 0 deletions test/test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10828,3 +10828,40 @@ TEST(HeaderSmugglingTest, ChunkedTrailerHeadersMerged) {
std::string res;
ASSERT_TRUE(send_request(1, req, &res));
}

#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(StaticFileSever, CacheValidation) {
for (
const std::string header_if_none_match :
{"*", R"("f", *)", R"(*, "i")",
R"("db88b784d27f0b92b63f0b3b159ea6f049b178546d99ae95f6f7b57c678c61c2d4b50af4374e81a09e812c2c957a5353803cef4c34aa36fe937ae643cc86bb4b")",
R"("d", "db88b784d27f0b92b63f0b3b159ea6f049b178546d99ae95f6f7b57c678c61c2d4b50af4374e81a09e812c2c957a5353803cef4c34aa36fe937ae643cc86bb4b")",
R"(W/"db88b784d27f0b92b63f0b3b159ea6f049b178546d99ae95f6f7b57c678c61c2d4b50af4374e81a09e812c2c957a5353803cef4c34aa36fe937ae643cc86bb4b", "g")"}) {
httplib::Server svr;
svr.set_mount_point("/", "./www/");

std::thread t = thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});

svr.wait_until_ready();

httplib::Client client(HOST, PORT);
const httplib::Result result =
client.Get("/file", Headers({{"If-None-Match", header_if_none_match}}));

ASSERT_NE(result, nullptr);
EXPECT_EQ(result.error(), Error::Success);
EXPECT_EQ(result->status, StatusCode::NotModified_304);

EXPECT_TRUE(result->has_header("ETag"));
EXPECT_EQ(
result->get_header_value("ETag"),
R"("db88b784d27f0b92b63f0b3b159ea6f049b178546d99ae95f6f7b57c678c61c2d4b50af4374e81a09e812c2c957a5353803cef4c34aa36fe937ae643cc86bb4b")");
EXPECT_TRUE(result->body.empty());
}
}
#endif