#include #include #include #include #include #include #include #include #include "server_internal.hxx" static constexpr std::size_t chunk_size = 1024*1024, max_image_size = 1024*1024*50, preview_size=480; static const std::set image_extension = {".png", ".jpg", ".jpeg", ".tga", ".bmp", ".psd", ".gif", ".jfif", ".pjpeg", ".pjp"}; struct UploadInfo { Server *server; std::shared_lock node_lock; std::size_t to_read; std::filesystem::path path; std::ofstream file; std::shared_ptr node; }; void make_preview(const std::shared_ptr& info) { int x, y, channels; auto img = std::unique_ptr {stbi_load(info->path.c_str(), &x, &y, &channels, 0), &free}; if (!img) return; float x_ration = (float)preview_size / (float)x, y_ration = (float)preview_size / (float)y; float ratio = std::min(x_ration, y_ration); int new_x = (int)((float)(x)*ratio), new_y = (int)((float)(y)*ratio); stbir_pixel_layout layout; switch (channels) { case 1: layout = STBIR_1CHANNEL; break; case 2: layout = STBIR_2CHANNEL; break; case 3: layout = STBIR_RGB; break; case 4: layout = STBIR_RGBA; break; default: return; } auto rimg = std::unique_ptr {stbir_resize_uint8_linear(img.get(), x, y, 0, nullptr, new_x, new_y, 0, layout), &free}; if (!rimg) return; auto png_path = info->path.replace_extension("png"); if (!stbi_write_png(png_path.c_str(), new_x, new_y, channels, rimg.get(), 0)) return; info->node->preview = true; } void fetch_handler(const std::shared_ptr &s, const restbed::Bytes &bytes) { std::shared_ptr info = s->get("upload"); std::size_t read = bytes.size(); info->to_read -= std::min(read, info->to_read); info->file.write((char*)bytes.data(), bytes.size()); if (info->to_read > 0) return s->fetch(std::min(info->to_read, chunk_size), fetch_handler); info->file.close(); s->close(200); std::size_t real_size = std::filesystem::file_size(info->path); info->node->size = real_size; auto ext = std::filesystem::path{info->node->name}.extension().string(); if (real_size < max_image_size && image_extension.contains(ext)) make_preview(info); info->node_lock.unlock(); info->server->save(); } void Server::upload(const std::shared_ptr &s) { const auto req = s->get_request(); if (!req->has_header("X-Node")) return s->close(400, "Missing node"); if (!req->has_header("X-Token")) return s->close(400, "Missing token"); if (req->get_header("Transfer-Encoding") == "chunked") { spdlog::error("Encountered a chunked upload!"); return s->close(500, "Sorry but your browser is not supported yet"); } std::uint64_t node_id = req->get_header("X-Node", 0); std::string token = req->get_header("X-Token"); 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"); if (!node->file) return s->close(400, "Can't upload to a directory"); std::size_t to_read = req->get_header("Content-Length", 0); auto path = user->user_dir / std::to_string(node->id); if (node->preview) { node->preview = false; std::filesystem::remove(path.replace_extension("png")); } std::shared_ptr info{new UploadInfo{ .server = this, .node_lock = std::shared_lock{user->node_lock}, .to_read = to_read, .path = path, .file = std::ofstream{path, std::ios_base::out|std::ios_base::trunc|std::ios_base::binary}, .node = node }}; s->set("upload", info); s->fetch(std::min(to_read, chunk_size), fetch_handler); } }