#include #include #include #include #include #include #include #include #include #include #include "server_internal.hxx" void Server::download(const std::shared_ptr &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 &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{ {"Content-Type", mime}, {"Content-Length", std::to_string(node->size)}, {"Content-Disposition", "attachment; filename=\"" + node->name + "\""} }, [user=user, node=node](const std::shared_ptr& 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 &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 &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 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> 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{ {"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::filesystem::path>> todo; for (const auto &node : nodes) todo.emplace_back(node, std::filesystem::path{}); auto handle_file = [&user, &archive](const std::pair, 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(); } }); }