From d5e2b5489d0f4e6e85733d08f68e12af029e2c34 Mon Sep 17 00:00:00 2001 From: Martchus Date: Tue, 15 Mar 2022 23:22:34 +0100 Subject: [PATCH] Provide filename for artefact downloads --- librepomgr/buildactions/buildaction.h | 3 +- .../buildactions/buildactionlivestreaming.cpp | 7 +++-- librepomgr/webapi/render.cpp | 30 ++++++++++++++----- librepomgr/webapi/render.h | 12 ++++---- librepomgr/webapi/routes_buildaction.cpp | 16 ++++++++-- librepomgr/webapi/session.cpp | 9 +++--- librepomgr/webapi/session.h | 5 ++-- 7 files changed, 56 insertions(+), 26 deletions(-) diff --git a/librepomgr/buildactions/buildaction.h b/librepomgr/buildactions/buildaction.h index 7ed8034..3d2e5bd 100644 --- a/librepomgr/buildactions/buildaction.h +++ b/librepomgr/buildactions/buildaction.h @@ -195,7 +195,8 @@ public: std::shared_ptr makeBuildProcess( std::string &&displayName, std::string &&logFilePath, ProcessHandler &&handler, AssociatedLocks &&locks = AssociatedLocks()); void terminateOngoingBuildProcesses(); - void streamFile(const WebAPI::Params ¶ms, const std::string &filePath, std::string_view fileMimeType); + void streamFile(const WebAPI::Params ¶ms, const std::string &filePath, boost::beast::string_view fileMimeType, + boost::beast::string_view contentDisposition = boost::beast::string_view()); ServiceSetup *setup(); protected: diff --git a/librepomgr/buildactions/buildactionlivestreaming.cpp b/librepomgr/buildactions/buildactionlivestreaming.cpp index 88c6e5c..a5af31b 100644 --- a/librepomgr/buildactions/buildactionlivestreaming.cpp +++ b/librepomgr/buildactions/buildactionlivestreaming.cpp @@ -407,7 +407,8 @@ void BuildAction::terminateOngoingBuildProcesses() } } -void BuildAction::streamFile(const WebAPI::Params ¶ms, const std::string &filePath, std::string_view fileMimeType) +void BuildAction::streamFile( + const WebAPI::Params ¶ms, const std::string &filePath, boost::beast::string_view fileMimeType, boost::beast::string_view contentDisposition) { auto buildProcess = std::shared_ptr(); if (const auto outputLock = std::unique_lock(m_outputSessionMutex); m_outputSession && m_outputSession->logFilePath() == filePath) { @@ -418,12 +419,12 @@ void BuildAction::streamFile(const WebAPI::Params ¶ms, const std::string &fi } if (!buildProcess) { // simply send the file if there's no ongoing process writing to it anymore - params.session.respond(filePath.data(), fileMimeType.data(), params.target.path); + params.session.respond(filePath.data(), fileMimeType, contentDisposition, params.target.path); return; } // stream the output of the ongoing process - auto chunkResponse = WebAPI::Render::makeChunkResponse(params.request(), fileMimeType.data()); + auto chunkResponse = WebAPI::Render::makeChunkResponse(params.request(), fileMimeType, contentDisposition); boost::beast::http::async_write_header(params.session.socket(), chunkResponse->serializer, [chunkResponse, filePath, buildProcess, session = params.session.shared_from_this()]( const boost::system::error_code &error, std::size_t) mutable { diff --git a/librepomgr/webapi/render.cpp b/librepomgr/webapi/render.cpp index d282b98..a6948bf 100644 --- a/librepomgr/webapi/render.cpp +++ b/librepomgr/webapi/render.cpp @@ -146,7 +146,7 @@ std::shared_ptr makeServerError(const Request &request, std::string_vi return res; } -std::shared_ptr makeData(const Request &request, const string &buffer, const char *mimeType) +std::shared_ptr makeData(const Request &request, const string &buffer, boost::beast::string_view mimeType) { const auto res = make_shared(http::status::ok, request.version()); res->set(http::field::server, BOOST_BEAST_VERSION_STRING); @@ -158,7 +158,7 @@ std::shared_ptr makeData(const Request &request, const string &buffer, return res; } -std::shared_ptr makeData(const Request &request, string &&buffer, const char *mimeType) +std::shared_ptr makeData(const Request &request, string &&buffer, boost::beast::string_view mimeType) { const auto res = make_shared(http::status::ok, request.version()); res->set(http::field::server, BOOST_BEAST_VERSION_STRING); @@ -170,11 +170,13 @@ std::shared_ptr makeData(const Request &request, string &&buffer, cons return res; } -std::shared_ptr makeData(const Request &request, rapidjson::StringBuffer &&buffer, const char *mimeType) +std::shared_ptr makeData(const Request &request, rapidjson::StringBuffer &&buffer, boost::beast::string_view mimeType) { const auto res = make_shared(http::status::ok, request.version()); res->set(http::field::server, BOOST_BEAST_VERSION_STRING); - res->set(http::field::content_type, mimeType); + if (!mimeType.empty()) { + res->set(http::field::content_type, mimeType); + } res->set(http::field::access_control_allow_origin, "*"); res->keep_alive(request.keep_alive()); res->body().assign(buffer.GetString(), buffer.GetSize()); @@ -182,11 +184,17 @@ std::shared_ptr makeData(const Request &request, rapidjson::StringBuff return res; } -std::shared_ptr makeFile(const Request &request, const char *filePath, const char *mimeType, boost::beast::error_code &ec) +std::shared_ptr makeFile(const Request &request, const char *filePath, boost::beast::string_view mimeType, + boost::beast::string_view contentDisposition, boost::beast::error_code &ec) { const auto fileResponse = std::make_shared(); fileResponse->set(http::field::server, BOOST_BEAST_VERSION_STRING); - fileResponse->set(http::field::content_type, mimeType); + if (!mimeType.empty()) { + fileResponse->set(http::field::content_type, mimeType); + } + if (!contentDisposition.empty()) { + fileResponse->set(http::field::content_disposition, contentDisposition); + } fileResponse->set(http::field::access_control_allow_origin, "*"); fileResponse->keep_alive(request.keep_alive()); fileResponse->body().open(filePath, file_mode::scan, ec); @@ -202,10 +210,16 @@ inline ChunkResponse::ChunkResponse() response.chunked(true); } -std::shared_ptr makeChunkResponse(const Request &request, const char *mimeType) +std::shared_ptr makeChunkResponse( + const Request &request, boost::beast::string_view mimeType, boost::beast::string_view contentDisposition) { const auto chunkResponse = std::make_shared(); - chunkResponse->response.set(boost::beast::http::field::content_type, mimeType); + if (!mimeType.empty()) { + chunkResponse->response.set(boost::beast::http::field::content_type, mimeType); + } + if (!contentDisposition.empty()) { + chunkResponse->response.set(boost::beast::http::field::content_disposition, contentDisposition); + } chunkResponse->response.keep_alive(request.keep_alive()); return chunkResponse; } diff --git a/librepomgr/webapi/render.h b/librepomgr/webapi/render.h index caa1f86..c3c8f94 100644 --- a/librepomgr/webapi/render.h +++ b/librepomgr/webapi/render.h @@ -31,14 +31,16 @@ std::shared_ptr makeNotFound(const Request &request, std::string_view std::shared_ptr makeAuthRequired(const Request &request); std::shared_ptr makeForbidden(const Request &request); std::shared_ptr makeServerError(const Request &request, std::string_view what); -std::shared_ptr makeData(const Request &request, const std::string &data, const char *mimeType); -std::shared_ptr makeData(const Request &request, std::string &&data, const char *mimeType); -std::shared_ptr makeData(const Request &request, RAPIDJSON_NAMESPACE::StringBuffer &&buffer, const char *mimeType); +std::shared_ptr makeData(const Request &request, const std::string &data, boost::beast::string_view mimeType); +std::shared_ptr makeData(const Request &request, std::string &&data, boost::beast::string_view mimeType); +std::shared_ptr makeData(const Request &request, RAPIDJSON_NAMESPACE::StringBuffer &&buffer, boost::beast::string_view mimeType); std::shared_ptr makeText(const Request &request, const std::string &text); std::shared_ptr makeText(const Request &request, std::string &&text); std::shared_ptr makeJson(const Request &request, std::string &&json); -std::shared_ptr makeFile(const Request &request, const char *filePath, const char *mimeType, boost::beast::error_code &ec); -std::shared_ptr makeChunkResponse(const Request &request, const char *mimeType); +std::shared_ptr makeFile(const Request &request, const char *filePath, boost::beast::string_view mimeType, + boost::beast::string_view contentDisposition, boost::beast::error_code &ec); +std::shared_ptr makeChunkResponse( + const Request &request, boost::beast::string_view mimeType, boost::beast::string_view contentDisposition); inline std::shared_ptr makeText(const Request &request, const std::string &text) { diff --git a/librepomgr/webapi/routes_buildaction.cpp b/librepomgr/webapi/routes_buildaction.cpp index c12e1b2..7c861c2 100644 --- a/librepomgr/webapi/routes_buildaction.cpp +++ b/librepomgr/webapi/routes_buildaction.cpp @@ -1,3 +1,5 @@ +#define CPP_UTILITIES_PATHHELPER_STRING_VIEW + #include "./params.h" #include "./render.h" #include "./routes.h" @@ -5,6 +7,7 @@ #include "../serversetup.h" #include +#include #include #include @@ -129,9 +132,16 @@ static void getBuildActionFile( if (name.empty()) { return; } - auto mimeType = std::string_view("application/octet-stream"); + auto mimeType = boost::beast::string_view("application/octet-stream"); + auto contentDisposition = std::string(); if (determineMimeType) { - mimeType = Session::determineMimeType(name, mimeType); + if (const auto detectedMimeType = Session::determineMimeType(name, boost::beast::string_view()); !detectedMimeType.empty()) { + mimeType = detectedMimeType; + } else { + auto fileName = CppUtilities::fileName(name); + findAndReplace(fileName, "\"", ""); + contentDisposition = "attachment; filename=\"" % fileName + "\""; + } } auto buildActionsSearchResult = findBuildActions(params, std::move(handler), false, 1); if (!buildActionsSearchResult.ok) { @@ -141,7 +151,7 @@ static void getBuildActionFile( auto &buildAction = buildActionsSearchResult.actions.front(); for (const auto &logFile : (*buildAction).*fileList) { if (name == logFile) { - buildAction->streamFile(params, name, mimeType); + buildAction->streamFile(params, name, mimeType, contentDisposition); return; } } diff --git a/librepomgr/webapi/session.cpp b/librepomgr/webapi/session.cpp index 843a181..b191ded 100644 --- a/librepomgr/webapi/session.cpp +++ b/librepomgr/webapi/session.cpp @@ -102,7 +102,7 @@ void Session::received(boost::system::error_code ec, size_t bytesTransferred) // handle requests to static files (intended for development only; use NGINX in production) if (!m_setup.webServer.staticFilesPath.empty() && (path.find("../") == string::npos || path.find("..\\") == string::npos)) { const auto filePath = argsToString(m_setup.webServer.staticFilesPath, params.target.path); - respond(filePath.data(), determineMimeType(params.target.path).data(), params.target.path); + respond(filePath.data(), determineMimeType(params.target.path).data(), boost::beast::string_view(), params.target.path); return; } @@ -128,11 +128,12 @@ void Session::respond(std::shared_ptr &&response) m_res = move(response); } -void Session::respond(const char *localFilePath, const char *mimeType, std::string_view urlPath) +void Session::respond( + const char *localFilePath, boost::beast::string_view mimeType, boost::beast::string_view contentDisposition, std::string_view urlPath) { // make response with file body auto ec = boost::beast::error_code{}; - auto response = Render::makeFile(m_parser->get(), localFilePath, mimeType, ec); + auto response = Render::makeFile(m_parser->get(), localFilePath, mimeType, contentDisposition, ec); if (ec.failed()) { respond(Render::makeNotFound(m_parser->get(), urlPath)); return; @@ -167,7 +168,7 @@ void Session::responded(boost::system::error_code ec, std::size_t bytesTransferr receive(); } -std::string_view Session::determineMimeType(std::string_view path, std::string_view fallback) +boost::beast::string_view Session::determineMimeType(std::string_view path, boost::beast::string_view fallback) { if (path.ends_with(".html")) { return "text/html"; diff --git a/librepomgr/webapi/session.h b/librepomgr/webapi/session.h index 27dbcdb..07a1bec 100644 --- a/librepomgr/webapi/session.h +++ b/librepomgr/webapi/session.h @@ -22,14 +22,15 @@ public: void receive(); void respond(std::shared_ptr &&response); - void respond(const char *localFilePath, const char *mimeType, std::string_view urlPath); + void respond( + const char *localFilePath, boost::beast::string_view mimeType, boost::beast::string_view contentDisposition, std::string_view urlPath); void close(); const Request &request() const; void assignEmptyRequest(); boost::asio::ip::tcp::socket &socket(); void received(boost::system::error_code ec, std::size_t bytesTransferred); void responded(boost::system::error_code ec, std::size_t bytesTransferred, bool shouldClose); - static std::string_view determineMimeType(std::string_view path, std::string_view fallback = "text/plain"); + static boost::beast::string_view determineMimeType(std::string_view path, boost::beast::string_view fallback = "text/plain"); private: boost::asio::ip::tcp::socket m_socket;