mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2025-05-10 01:33:53 +00:00
Fix #1559
This commit is contained in:
parent
00a8cb8e5d
commit
6bb580cda8
79
httplib.h
79
httplib.h
@ -90,6 +90,10 @@
|
|||||||
#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u)
|
#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef CPPHTTPLIB_SEND_BUFSIZ
|
||||||
|
#define CPPHTTPLIB_SEND_BUFSIZ size_t(4096u)
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
|
#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
|
||||||
#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
|
#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
|
||||||
#endif
|
#endif
|
||||||
@ -784,6 +788,7 @@ public:
|
|||||||
bool remove_mount_point(const std::string &mount_point);
|
bool remove_mount_point(const std::string &mount_point);
|
||||||
Server &set_file_extension_and_mimetype_mapping(const std::string &ext,
|
Server &set_file_extension_and_mimetype_mapping(const std::string &ext,
|
||||||
const std::string &mime);
|
const std::string &mime);
|
||||||
|
Server &set_default_file_mimetype(const std::string &mime);
|
||||||
Server &set_file_request_handler(Handler handler);
|
Server &set_file_request_handler(Handler handler);
|
||||||
|
|
||||||
Server &set_error_handler(HandlerWithResponse handler);
|
Server &set_error_handler(HandlerWithResponse handler);
|
||||||
@ -907,6 +912,7 @@ private:
|
|||||||
};
|
};
|
||||||
std::vector<MountPointEntry> base_dirs_;
|
std::vector<MountPointEntry> base_dirs_;
|
||||||
std::map<std::string, std::string> file_extension_and_mimetype_map_;
|
std::map<std::string, std::string> file_extension_and_mimetype_map_;
|
||||||
|
std::string default_file_mimetype_ = "application/octet-stream";
|
||||||
Handler file_request_handler_;
|
Handler file_request_handler_;
|
||||||
|
|
||||||
Handlers get_handlers_;
|
Handlers get_handlers_;
|
||||||
@ -3197,9 +3203,10 @@ inline constexpr unsigned int operator"" _t(const char *s, size_t l) {
|
|||||||
|
|
||||||
} // namespace udl
|
} // namespace udl
|
||||||
|
|
||||||
inline const char *
|
inline std::string
|
||||||
find_content_type(const std::string &path,
|
find_content_type(const std::string &path,
|
||||||
const std::map<std::string, std::string> &user_data) {
|
const std::map<std::string, std::string> &user_data,
|
||||||
|
const std::string &default_content_type) {
|
||||||
auto ext = file_extension(path);
|
auto ext = file_extension(path);
|
||||||
|
|
||||||
auto it = user_data.find(ext);
|
auto it = user_data.find(ext);
|
||||||
@ -3208,7 +3215,8 @@ find_content_type(const std::string &path,
|
|||||||
using udl::operator""_t;
|
using udl::operator""_t;
|
||||||
|
|
||||||
switch (str2tag(ext)) {
|
switch (str2tag(ext)) {
|
||||||
default: return nullptr;
|
default: return default_content_type;
|
||||||
|
|
||||||
case "css"_t: return "text/css";
|
case "css"_t: return "text/css";
|
||||||
case "csv"_t: return "text/csv";
|
case "csv"_t: return "text/csv";
|
||||||
case "htm"_t:
|
case "htm"_t:
|
||||||
@ -4489,12 +4497,13 @@ get_range_offset_and_length(const Request &req, size_t content_length,
|
|||||||
return std::make_pair(r.first, static_cast<size_t>(r.second - r.first) + 1);
|
return std::make_pair(r.first, static_cast<size_t>(r.second - r.first) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::string make_content_range_header_field(size_t offset, size_t length,
|
inline std::string
|
||||||
|
make_content_range_header_field(const std::pair<ssize_t, ssize_t> &range,
|
||||||
size_t content_length) {
|
size_t content_length) {
|
||||||
std::string field = "bytes ";
|
std::string field = "bytes ";
|
||||||
field += std::to_string(offset);
|
if (range.first != -1) { field += std::to_string(range.first); }
|
||||||
field += "-";
|
field += "-";
|
||||||
field += std::to_string(offset + length - 1);
|
if (range.second != -1) { field += std::to_string(range.second); }
|
||||||
field += "/";
|
field += "/";
|
||||||
field += std::to_string(content_length);
|
field += std::to_string(content_length);
|
||||||
return field;
|
return field;
|
||||||
@ -4516,14 +4525,15 @@ bool process_multipart_ranges_data(const Request &req, Response &res,
|
|||||||
ctoken("\r\n");
|
ctoken("\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto offsets = get_range_offset_and_length(req, res.body.size(), i);
|
ctoken("Content-Range: ");
|
||||||
|
const auto &range = req.ranges[i];
|
||||||
|
stoken(make_content_range_header_field(range, res.content_length_));
|
||||||
|
ctoken("\r\n");
|
||||||
|
ctoken("\r\n");
|
||||||
|
|
||||||
|
auto offsets = get_range_offset_and_length(req, res.content_length_, i);
|
||||||
auto offset = offsets.first;
|
auto offset = offsets.first;
|
||||||
auto length = offsets.second;
|
auto length = offsets.second;
|
||||||
|
|
||||||
ctoken("Content-Range: ");
|
|
||||||
stoken(make_content_range_header_field(offset, length, res.body.size()));
|
|
||||||
ctoken("\r\n");
|
|
||||||
ctoken("\r\n");
|
|
||||||
if (!content(offset, length)) { return false; }
|
if (!content(offset, length)) { return false; }
|
||||||
ctoken("\r\n");
|
ctoken("\r\n");
|
||||||
}
|
}
|
||||||
@ -5493,6 +5503,11 @@ Server::set_file_extension_and_mimetype_mapping(const std::string &ext,
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Server &Server::set_default_file_mimetype(const std::string &mime) {
|
||||||
|
default_file_mimetype_ = mime;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
inline Server &Server::set_file_request_handler(Handler handler) {
|
inline Server &Server::set_file_request_handler(Handler handler) {
|
||||||
file_request_handler_ = std::move(handler);
|
file_request_handler_ = std::move(handler);
|
||||||
return *this;
|
return *this;
|
||||||
@ -5944,17 +5959,35 @@ inline bool Server::handle_file_request(const Request &req, Response &res,
|
|||||||
if (path.back() == '/') { path += "index.html"; }
|
if (path.back() == '/') { path += "index.html"; }
|
||||||
|
|
||||||
if (detail::is_file(path)) {
|
if (detail::is_file(path)) {
|
||||||
detail::read_file(path, res.body);
|
|
||||||
auto type =
|
|
||||||
detail::find_content_type(path, file_extension_and_mimetype_map_);
|
|
||||||
if (type) { res.set_header("Content-Type", type); }
|
|
||||||
for (const auto &kv : entry.headers) {
|
for (const auto &kv : entry.headers) {
|
||||||
res.set_header(kv.first.c_str(), kv.second);
|
res.set_header(kv.first.c_str(), kv.second);
|
||||||
}
|
}
|
||||||
res.status = req.has_header("Range") ? 206 : 200;
|
|
||||||
|
auto fs =
|
||||||
|
std::make_shared<std::ifstream>(path, std::ios_base::binary);
|
||||||
|
|
||||||
|
fs->seekg(0, std::ios_base::end);
|
||||||
|
auto size = static_cast<size_t>(fs->tellg());
|
||||||
|
fs->seekg(0);
|
||||||
|
|
||||||
|
res.set_content_provider(
|
||||||
|
size,
|
||||||
|
detail::find_content_type(path, file_extension_and_mimetype_map_,
|
||||||
|
default_file_mimetype_),
|
||||||
|
[fs](size_t offset, size_t length, DataSink &sink) -> bool {
|
||||||
|
std::array<char, CPPHTTPLIB_SEND_BUFSIZ> buf{};
|
||||||
|
length = std::min(length, CPPHTTPLIB_SEND_BUFSIZ);
|
||||||
|
|
||||||
|
fs->seekg(static_cast<std::streamsize>(offset), std::ios_base::beg);
|
||||||
|
fs->read(buf.data(), static_cast<std::streamsize>(length));
|
||||||
|
sink.write(buf.data(), length);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
if (!head && file_request_handler_) {
|
if (!head && file_request_handler_) {
|
||||||
file_request_handler_(req, res);
|
file_request_handler_(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6202,10 +6235,10 @@ inline void Server::apply_ranges(const Request &req, Response &res,
|
|||||||
} else if (req.ranges.size() == 1) {
|
} else if (req.ranges.size() == 1) {
|
||||||
auto offsets =
|
auto offsets =
|
||||||
detail::get_range_offset_and_length(req, res.content_length_, 0);
|
detail::get_range_offset_and_length(req, res.content_length_, 0);
|
||||||
auto offset = offsets.first;
|
|
||||||
length = offsets.second;
|
length = offsets.second;
|
||||||
|
|
||||||
auto content_range = detail::make_content_range_header_field(
|
auto content_range = detail::make_content_range_header_field(
|
||||||
offset, length, res.content_length_);
|
req.ranges[0], res.content_length_);
|
||||||
res.set_header("Content-Range", content_range);
|
res.set_header("Content-Range", content_range);
|
||||||
} else {
|
} else {
|
||||||
length = detail::get_multipart_ranges_data_length(req, res, boundary,
|
length = detail::get_multipart_ranges_data_length(req, res, boundary,
|
||||||
@ -6228,13 +6261,15 @@ inline void Server::apply_ranges(const Request &req, Response &res,
|
|||||||
if (req.ranges.empty()) {
|
if (req.ranges.empty()) {
|
||||||
;
|
;
|
||||||
} else if (req.ranges.size() == 1) {
|
} else if (req.ranges.size() == 1) {
|
||||||
|
auto content_range = detail::make_content_range_header_field(
|
||||||
|
req.ranges[0], res.body.size());
|
||||||
|
res.set_header("Content-Range", content_range);
|
||||||
|
|
||||||
auto offsets =
|
auto offsets =
|
||||||
detail::get_range_offset_and_length(req, res.body.size(), 0);
|
detail::get_range_offset_and_length(req, res.body.size(), 0);
|
||||||
auto offset = offsets.first;
|
auto offset = offsets.first;
|
||||||
auto length = offsets.second;
|
auto length = offsets.second;
|
||||||
auto content_range = detail::make_content_range_header_field(
|
|
||||||
offset, length, res.body.size());
|
|
||||||
res.set_header("Content-Range", content_range);
|
|
||||||
if (offset < res.body.size()) {
|
if (offset < res.body.size()) {
|
||||||
res.body = res.body.substr(offset, length);
|
res.body = res.body.substr(offset, length);
|
||||||
} else {
|
} else {
|
||||||
|
38
test/test.cc
38
test/test.cc
@ -2511,7 +2511,43 @@ TEST_F(ServerTest, StaticFileRanges) {
|
|||||||
.find(
|
.find(
|
||||||
"multipart/byteranges; boundary=--cpp-httplib-multipart-data-") ==
|
"multipart/byteranges; boundary=--cpp-httplib-multipart-data-") ==
|
||||||
0);
|
0);
|
||||||
EXPECT_EQ("266", res->get_header_value("Content-Length"));
|
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) {
|
TEST_F(ServerTest, InvalidBaseDirMount) {
|
||||||
|
8192
test/www/dir/1MB.txt
Normal file
8192
test/www/dir/1MB.txt
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user