2023-10-20 13:02:21 +02:00
|
|
|
#include <filesystem>
|
|
|
|
#include <string_view>
|
|
|
|
#include <ranges>
|
|
|
|
#include <charconv>
|
2024-04-24 13:44:37 +02:00
|
|
|
#include <deque>
|
2024-04-23 15:38:41 +02:00
|
|
|
#include "../util/miniz.hxx"
|
2023-10-20 13:02:21 +02:00
|
|
|
#include "server_internal.hxx"
|
|
|
|
|
2024-04-23 15:38:41 +02:00
|
|
|
net::awaitable<void> Server::download(tcp_stream &s, const http::request<http::string_body> &req) {
|
|
|
|
auto body = req.body();
|
|
|
|
if (body.empty()) { co_await send_error(s, req, "Empty body"); co_return; }
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2023-10-20 13:02:21 +02:00
|
|
|
|
2024-04-23 15:38:41 +02:00
|
|
|
if (node_str.empty()) { co_await send_error(s, req, "Missing node"); co_return; }
|
|
|
|
if (token.empty()) { co_await send_error(s, req, "Missing token"); co_return; }
|
2023-10-20 13:02:21 +02:00
|
|
|
|
2024-04-23 15:38:41 +02:00
|
|
|
std::uint64_t node_id;
|
|
|
|
auto fc_res = std::from_chars(node_str.data(), node_str.data() + node_str.size(), node_id);
|
|
|
|
if (fc_res.ec != std::errc{}) { co_await send_error(s, req, "Invalid node"); co_return; }
|
|
|
|
|
|
|
|
check_user() { co_await send_error(s, req, "Invalid user"); co_return; }
|
|
|
|
|
|
|
|
std::shared_lock lock{user->node_lock};
|
|
|
|
auto node = get_node(user, node_id);
|
|
|
|
if (!node) { co_await send_error(s, req, "Invalid node"); co_return; }
|
|
|
|
|
2024-05-21 14:51:58 +02:00
|
|
|
s.expires_never();
|
|
|
|
|
2024-04-23 15:38:41 +02:00
|
|
|
beast::error_code ec;
|
|
|
|
auto mime = get_mime_type(node->name);
|
|
|
|
auto res = create_response<http::status::ok, http::file_body, http::string_body>(req);
|
|
|
|
res.content_length(node->size);
|
|
|
|
res.set(http::field::content_type, mime);
|
|
|
|
res.set(http::field::content_disposition, "attachment; filename=\"" + node->name + "\"");
|
|
|
|
res.body().open((user->user_dir / std::to_string(node->id)).c_str(), beast::file_mode::read, ec);
|
|
|
|
co_await http::async_write(s, res, net::use_awaitable);
|
2024-05-21 14:51:58 +02:00
|
|
|
|
|
|
|
s.expires_after(std::chrono::seconds(30));
|
2023-10-20 13:02:21 +02:00
|
|
|
}
|
|
|
|
|
2024-04-23 15:38:41 +02:00
|
|
|
struct Zip : public ZipArchive {
|
|
|
|
tcp_stream *s;
|
|
|
|
http::response<http::buffer_body> *body;
|
|
|
|
http::response_serializer<http::buffer_body> *sr;
|
|
|
|
|
|
|
|
Zip(tcp_stream *s, http::response<http::buffer_body> *body, http::response_serializer<http::buffer_body> *sr)
|
|
|
|
: s(s), body(body), sr(sr) {}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
net::awaitable<void> write(const void *pVoid, size_t n) override {
|
|
|
|
if (n == 0)
|
|
|
|
co_return;
|
|
|
|
|
|
|
|
body->body().data = (void*)pVoid;
|
|
|
|
body->body().size = n;
|
|
|
|
body->body().more = true;
|
|
|
|
|
|
|
|
try {
|
|
|
|
co_await http::async_write(*s, *sr, net::use_awaitable);
|
|
|
|
} catch (beast::system_error &se) {
|
|
|
|
if (se.code() != http::error::need_buffer)
|
|
|
|
throw;
|
2023-10-20 13:02:21 +02:00
|
|
|
}
|
2024-04-23 15:38:41 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
net::awaitable<void> Server::download_multi(tcp_stream &s, const http::request<http::string_body> &req) {
|
|
|
|
auto body = req.body();
|
|
|
|
if (body.empty()) { co_await send_error(s, req, "Empty body"); co_return; }
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2023-10-20 13:02:21 +02:00
|
|
|
|
2024-04-23 15:38:41 +02:00
|
|
|
if (nodes_str.empty()) { co_await send_error(s, req, "Missing nodes"); co_return; }
|
|
|
|
if (token.empty()) { co_await send_error(s, req, "Missing token"); co_return; }
|
|
|
|
|
|
|
|
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{}) { co_await send_error(s, req, "Invalid node " + std::string{std::string_view{part}}); co_return; }
|
|
|
|
node_ids.push_back(node_id);
|
|
|
|
}
|
2023-10-20 13:02:21 +02:00
|
|
|
|
2024-04-23 15:38:41 +02:00
|
|
|
check_user() { co_await send_error(s, req, "Invalid user"); co_return; }
|
|
|
|
|
|
|
|
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) { co_await send_error(s, req, "Invalid node " + std::to_string(node_id)); co_return; }
|
|
|
|
nodes.push_back(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto res = create_response<http::status::ok, http::buffer_body, http::string_body>(req);
|
|
|
|
res.chunked(true);
|
|
|
|
res.set(http::field::content_type, "application/zip");
|
|
|
|
res.set(http::field::content_disposition, "attachment; filename=\"files.zip\"");
|
|
|
|
res.body().data = nullptr;
|
|
|
|
res.body().more = true;
|
|
|
|
|
2024-05-21 14:51:58 +02:00
|
|
|
s.expires_never();
|
|
|
|
|
2024-04-23 15:38:41 +02:00
|
|
|
http::response_serializer<http::buffer_body> sr{res};
|
|
|
|
|
|
|
|
co_await http::async_write_header(s, sr, net::use_awaitable);
|
|
|
|
|
|
|
|
Zip zip{&s, &res, &sr};
|
|
|
|
|
|
|
|
std::deque<std::pair<std::shared_ptr<Node>, std::filesystem::path>> todo;
|
|
|
|
for (const auto &node : nodes)
|
|
|
|
todo.emplace_back(node, std::filesystem::path{});
|
|
|
|
|
|
|
|
while (!todo.empty()) {
|
|
|
|
const auto &node = todo.front();
|
|
|
|
if (node.first->file) {
|
|
|
|
auto path = node.second / node.first->name;
|
|
|
|
auto real_path = user->user_dir / std::to_string(node.first->id);
|
|
|
|
co_await zip.add_file(real_path, path);
|
|
|
|
} else {
|
|
|
|
auto path = node.second / node.first->name;
|
|
|
|
auto dir_path = path.string() + "/";
|
|
|
|
co_await zip.add_dir(dir_path);
|
|
|
|
for (const auto &child : node.first->children) {
|
|
|
|
auto p = std::make_pair(child, path);
|
|
|
|
todo.push_back(p);
|
|
|
|
}
|
2023-10-20 13:02:21 +02:00
|
|
|
}
|
2024-04-23 15:38:41 +02:00
|
|
|
todo.pop_front();
|
|
|
|
}
|
|
|
|
co_await zip.end();
|
|
|
|
|
|
|
|
res.body().data = nullptr;
|
|
|
|
res.body().more = false;
|
|
|
|
co_await http::async_write(s, sr, net::use_awaitable);
|
2024-05-21 14:51:58 +02:00
|
|
|
|
|
|
|
s.expires_after(std::chrono::seconds(30));
|
2023-10-20 13:02:21 +02:00
|
|
|
}
|