#include "put_handler.hpp" #include "hash.hpp" #include "compress.hpp" #include "string_utils.hpp" #include "utils.hpp" #include #include #include #include namespace simple_object_storage { PutHandler::PutHandler(Server& server) : server_(server) {} void PutHandler::handle_put_object(const httplib::Request& req, httplib::Response& res) { // Check all request parameters first before processing any data std::map params; if (!server_.validate_write_request(req, res, {}, params)) { // No required params now since token is in header return; } // 1. Check we're in the /upload path if (req.path != "/upload") { res.status = 404; nlohmann::json response = {{"result", "error"}, {"error", "Not found - put requests must be to /upload"}}; res.set_content(response.dump(), "application/json"); return; } // Parse the multipart form data if (!req.has_file("file")) { res.status = 400; nlohmann::json response = {{"result", "error"}, {"error", "No file provided in upload"}}; res.set_content(response.dump(), "application/json"); return; } // Get the file data const auto& file = req.get_file_value("file"); // Parse metadata if provided nlohmann::json metadata; if (req.has_file("metadata")) { try { const auto& metadata_file = req.get_file_value("metadata"); metadata = nlohmann::json::parse(metadata_file.content); } catch (const nlohmann::json::parse_error& e) { res.status = 400; nlohmann::json response = {{"result", "error"}, {"error", "Invalid JSON metadata: " + std::string(e.what())}}; res.set_content(response.dump(), "application/json"); return; } } // Validate required metadata fields if (!metadata.contains("labeltag")) { res.status = 400; nlohmann::json response = {{"result", "error"}, {"error", "Missing required metadata field: labeltag"}}; res.set_content(response.dump(), "application/json"); return; } // Extract labeltag and validate format std::string labeltag = metadata["labeltag"]; auto [label, tag] = server_.parse_label_tag(labeltag); if (label.empty() || tag.empty()) { res.status = 400; nlohmann::json response = {{"result", "error"}, {"error", "Invalid label:tag format"}}; res.set_content(response.dump(), "application/json"); return; } // Add filename to metadata if not provided if (!metadata.contains("filename")) { metadata["filename"] = file.filename; } // Now that all parameters are validated, process the upload // Generate a random number for the temporary filename std::mt19937_64 rng(std::chrono::high_resolution_clock::now().time_since_epoch().count()); std::uniform_int_distribution dist; uint64_t random_num = dist(rng); std::string temp_filename = "temp_" + std::to_string(random_num); // Create temporary file std::filesystem::path temp_path = server_.config_.object_store_path / temp_filename; std::ofstream temp_file(temp_path, std::ios::binary); if (!temp_file.is_open()) { res.status = 500; nlohmann::json response = {{"result", "error"}, {"error", "Failed to create temporary file"}}; res.set_content(response.dump(), "application/json"); return; } // Write file content to temporary file if (!temp_file.write(file.content.c_str(), file.content.size())) { res.status = 500; nlohmann::json response = {{"result", "error"}, {"error", "Failed to write to temporary file"}}; res.set_content(response.dump(), "application/json"); temp_file.close(); std::filesystem::remove(temp_path); return; } temp_file.close(); // Ensure the temporary file is removed even if errors occur ScopeFileDeleter temp_file_deleter(temp_path); // Calculate hash uint64_t hash = hash_file(temp_path.string()); if (hash == 0) { res.status = 500; nlohmann::json response = {{"result", "error"}, {"error", "Failed to calculate hash"}}; res.set_content(response.dump(), "application/json"); return; } // Add file metadata add_file_metadata(temp_path.string(), metadata); // Check if filename ends with ".tgz" using the utility function if (utils::ends_with(metadata["filename"], ".tgz")) { metadata["tgz_content_hash"] = get_hash_from_tgz(temp_path.string()); } // Move file to final location std::filesystem::path final_path = server_.config_.object_store_path / std::to_string(hash); if (!std::filesystem::exists(final_path)) { try { std::filesystem::rename(temp_path, final_path); temp_file_deleter.release(); } catch (const std::filesystem::filesystem_error& e) { std::cerr << "Error renaming temp file: " << e.what() << std::endl; res.status = 500; nlohmann::json response = {{"result", "error"}, {"error", "Failed to store object file"}}; res.set_content(response.dump(), "application/json"); return; } } // Update database index dbEntry entry; entry.label_tag = labeltag; entry.hash = std::to_string(hash); entry.metadata = metadata; // Store the complete metadata if (!server_.db_->update_or_insert(entry)) { res.status = 500; nlohmann::json response = {{"result", "error"}, {"error", "Failed to update database index"}}; res.set_content(response.dump(), "application/json"); // Attempt to clean up the moved file if index fails try { if (std::filesystem::exists(final_path)) std::filesystem::remove(final_path); } catch(...) {}; return; } res.set_content(nlohmann::json({{"result", "success"}, {"hash", std::to_string(hash)}}).dump(), "application/json"); } void PutHandler::add_file_metadata(const std::string& file_path, nlohmann::json& metadata) const { // get the file size metadata["file_size"] = std::filesystem::file_size(file_path); // get the file modification time auto ftime = std::filesystem::last_write_time(file_path); auto sctp = std::chrono::time_point_cast( ftime - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now() ); metadata["file_modification_time"] = std::chrono::system_clock::to_time_t(sctp); } } // namespace simple_object_storage