mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-19 11:19:36 +00:00
Read served files with pread
Over TLS a file is copied out of its mapping in this process, so one truncated while it was being served raised SIGBUS and took the whole server down. Read it with pread, which comes up short instead.
This commit is contained in:
1 parent
3782a9f1cd
commit
44658e660b
2 files changed
+65
-3
No files matched your search
@@ -3988,6 +3988,10 @@ public:
|
|||||||
bool is_open() const;
|
bool is_open() const;
|
||||||
size_t size() const;
|
size_t size() const;
|
||||||
const char *data() const;
|
const char *data() const;
|
||||||
|
// Copies up to len bytes from offset into buf and returns how many. Unlike
|
||||||
|
// reading data(), it gives fewer, down to 0, for a file that has shrunk
|
||||||
|
// since it was opened, rather than a SIGBUS.
|
||||||
|
size_t read(size_t offset, char *buf, size_t len) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
@@ -6520,6 +6524,22 @@ inline const char *mmap::data() const {
|
|||||||
return is_open_empty_file ? "" : static_cast<const char *>(addr_);
|
return is_open_empty_file ? "" : static_cast<const char *>(addr_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline size_t mmap::read(size_t offset, char *buf, size_t len) const {
|
||||||
|
if (offset >= size_) { return 0; }
|
||||||
|
len = (std::min)(len, size_ - offset);
|
||||||
|
#if defined(_WIN32)
|
||||||
|
// Windows does not let a mapped file be truncated.
|
||||||
|
std::memcpy(buf, static_cast<const char *>(addr_) + offset, len);
|
||||||
|
return len;
|
||||||
|
#else
|
||||||
|
ssize_t n;
|
||||||
|
do {
|
||||||
|
n = ::pread(fd_, buf, len, static_cast<off_t>(offset));
|
||||||
|
} while (n < 0 && errno == EINTR);
|
||||||
|
return n > 0 ? static_cast<size_t>(n) : 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
inline void mmap::close() {
|
inline void mmap::close() {
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
if (addr_) {
|
if (addr_) {
|
||||||
@@ -8991,9 +9011,12 @@ inline void set_file_content_provider(Response &res,
|
|||||||
res.set_content_provider(
|
res.set_content_provider(
|
||||||
m->size(), content_type,
|
m->size(), content_type,
|
||||||
[m](size_t offset, size_t length, DataSink &sink) -> bool {
|
[m](size_t offset, size_t length, DataSink &sink) -> bool {
|
||||||
// Never read past the mapping.
|
// Never read past the file, which ends the response early if it has
|
||||||
if (offset >= m->size()) { return false; }
|
// shrunk since it was opened.
|
||||||
sink.write(m->data() + offset, (std::min)(length, m->size() - offset));
|
char buf[CPPHTTPLIB_SEND_BUFSIZ];
|
||||||
|
auto n = m->read(offset, buf, (std::min)(length, sizeof(buf)));
|
||||||
|
if (n == 0) { return false; }
|
||||||
|
sink.write(buf, n);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12797,6 +12797,45 @@ TEST(ResponseContentSourceTest, LastContentSetterWins) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(CPPHTTPLIB_SSL_ENABLED) && !defined(_WIN32)
|
||||||
|
TEST(FileContentTest, TruncatedWhileServedEndsTheResponse) {
|
||||||
|
const char *path = "./truncated_while_served.txt";
|
||||||
|
const size_t size = 32 * 1024 * 1024;
|
||||||
|
{
|
||||||
|
std::ofstream f(path, std::ios::binary);
|
||||||
|
f << std::string(size, 'x');
|
||||||
|
}
|
||||||
|
auto se_file = detail::scope_exit([&] { std::remove(path); });
|
||||||
|
|
||||||
|
// Over TLS, where the bytes are encrypted in this process rather than
|
||||||
|
// copied by send().
|
||||||
|
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||||
|
svr.Get("/file", [&](const Request &, Response &res) {
|
||||||
|
res.set_file_content(path, "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();
|
||||||
|
|
||||||
|
// The file shrinks once the first bytes are in, as a rewrite in place
|
||||||
|
// would, and reading the mapping past its new end used to raise SIGBUS.
|
||||||
|
SSLClient cli(HOST, PORT);
|
||||||
|
cli.enable_server_certificate_verification(false);
|
||||||
|
size_t received = 0;
|
||||||
|
auto res = cli.Get("/file", [&](const char *, size_t len) {
|
||||||
|
if (received == 0) { EXPECT_EQ(0, ::truncate(path, 0)); }
|
||||||
|
received += len;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
EXPECT_FALSE(res);
|
||||||
|
EXPECT_LT(received, size);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Forward declaration (see the base64_encode note).
|
// Forward declaration (see the base64_encode note).
|
||||||
namespace httplib {
|
namespace httplib {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|||||||
Reference in new issue
Block a user