184 lines
7.2 KiB
C++
Raw Normal View History

#include <filesystem>
#include <string_view>
#include <ranges>
#include <fstream>
#include <deque>
#include <charconv>
#include <miniz.h>
#include <corvusoft/restbed/session.hpp>
#include <corvusoft/restbed/request.hpp>
#include <corvusoft/restbed/response.hpp>
#include "server_internal.hxx"
void Server::download(const std::shared_ptr<restbed::Session> &s) {
const auto req = s->get_request();
std::size_t body_len = req->get_header("Content-Length", 0);
s->fetch(body_len, [this](const std::shared_ptr<restbed::Session> &s, const restbed::Bytes &b){
std::string body{b.cbegin(), b.cend()};
if (body.empty())
return s->close(400, "empty body");
std::string node_str, token;
for (const auto part : std::views::split(body, '&')) {
std::string_view part_view{part};
auto equal_pos = part_view.find_first_of('=');
auto key = part_view.substr(0, equal_pos);
if (key == "node")
node_str = part_view.substr(equal_pos+1);
else if (key == "token")
token = part_view.substr(equal_pos+1);
}
if (node_str.empty())
return s->close(400, "Missing node");
if (token.empty())
return s->close(400, "Missing token");
std::uint64_t node_id;
auto res = std::from_chars(node_str.data(), node_str.data() + node_str.size(), node_id);
if (res.ec != std::errc{})
return s->close(400, "Invalid node");
check_user() return s->close(400, "Invalid user");
{
std::shared_lock lock{user->node_lock};
auto node = get_node(user, node_id);
if (!node) return s->close(400, "Invalid node");
auto mime = get_mime_type(node->name);
s->yield(
200,
"",
std::multimap<std::string, std::string>{
{"Content-Type", mime},
{"Content-Length", std::to_string(node->size)},
{"Content-Disposition", "attachment; filename=\"" + node->name + "\""}
},
[&](const std::shared_ptr<restbed::Session>& s) {
std::shared_lock lock{user->node_lock};
restbed::Bytes buf(1024*1024*4, 0);
std::ifstream f{user->user_dir / std::to_string(node->id)};
while (!f.eof()) {
f.read((char*)buf.data(), buf.size());
buf.resize(f.gcount());
s->yield(buf);
}
s->close();
}
);
}
});
}
size_t zip_write_func(void *pOpaque, mz_uint64 _file_ofs, const void *pBuf, size_t n) {
auto s = (restbed::Session*)pOpaque;
if (n > 0) {
restbed::Bytes buf(n, 0);
std::memcpy(buf.data(), pBuf, n);
std::stringstream ss;
ss << std::hex << n;
s->yield(ss.str() + "\r\n");
s->yield(buf);
s->yield("\r\n");
}
return n;
}
void Server::download_multi(const std::shared_ptr<restbed::Session> &s) {
const auto req = s->get_request();
const auto body_len = req->get_header("Content-Length", 0);
s->fetch(body_len, [this](const std::shared_ptr<restbed::Session> &s, const restbed::Bytes &b){
std::string body{b.cbegin(), b.cend()};
if (body.empty())
return s->close(400, "empty body");
std::string nodes_str, token;
for (const auto part : std::views::split(body, '&')) {
std::string_view part_view{part};
auto equal_pos = part_view.find_first_of('=');
auto key = part_view.substr(0, equal_pos);
if (key == "nodes")
nodes_str = part_view.substr(equal_pos+1);
else if (key == "token")
token = part_view.substr(equal_pos+1);
}
if (nodes_str.empty())
return s->close(400, "Missing nodes");
if (token.empty())
return s->close(400, "Missing token");
std::vector<std::uint64_t> node_ids;
for (const auto part : std::views::split(nodes_str, '.')) {
std::uint64_t node_id;
auto res = std::from_chars(part.data(), part.data() + part.size(), node_id);
if (res.ec != std::errc{})
return s->close(400, "Invalid node " + std::string{std::string_view{part}});
node_ids.push_back(node_id);
}
check_user() return s->close(400, "Invalid user");
{
std::shared_lock lock{user->node_lock};
std::vector<std::shared_ptr<Node>> nodes;
for (auto node_id : node_ids) {
auto node = get_node(user, node_id);
if (!node) return s->close(400, "Invalid node " + std::to_string(node_id));
nodes.push_back(node);
}
s->yield(
200,
"",
std::multimap<std::string, std::string>{
{"Content-Type", "application/zip"},
{"Content-Disposition", "attachment; filename=\"files.zip\""},
{"Transfer-Encoding", "chunked"}
}
);
std::thread zip_thread{[nodes = nodes, user = user, s = s] {
std::shared_lock lock{user->node_lock};
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
archive.m_pWrite = zip_write_func;
archive.m_pIO_opaque = s.get();
mz_zip_writer_init_v2(&archive, 0, MZ_ZIP_FLAG_WRITE_ZIP64);
std::deque<std::pair<std::shared_ptr<Node>, std::filesystem::path>> todo;
for (const auto &node : nodes)
todo.emplace_back(node, std::filesystem::path{});
auto handle_file = [&user, &archive](const std::pair<std::shared_ptr<Node>, std::filesystem::path> &i) {
auto path = i.second / i.first->name;
auto real_path = user->user_dir / std::to_string(i.first->id);
mz_zip_writer_add_file(&archive, path.c_str(), real_path.c_str(), nullptr, 0, MZ_DEFAULT_COMPRESSION);
};
while (!todo.empty()) {
const auto &node = todo.front();
if (node.first->file) {
handle_file(node);
} else {
auto path = node.second / node.first->name;
auto dir_path = path.string() + "/";
mz_zip_writer_add_mem(&archive, dir_path.c_str(), nullptr, 0, 0);
for (const auto &child : node.first->children) {
auto p = std::make_pair(child, path);
if (child->file)
handle_file(p);
else
todo.push_back(p);
}
}
todo.pop_front();
}
mz_zip_writer_finalize_archive(&archive);
mz_zip_writer_end(&archive);
s->close("0\r\n\r\n");
}};
zip_thread.detach();
}
});
}