Provide filename for artefact downloads

This commit is contained in:
Martchus 2022-03-15 23:22:34 +01:00
parent 69a81f7583
commit d5e2b5489d
7 changed files with 56 additions and 26 deletions

View File

@ -195,7 +195,8 @@ public:
std::shared_ptr<BuildProcessSession> makeBuildProcess( std::shared_ptr<BuildProcessSession> makeBuildProcess(
std::string &&displayName, std::string &&logFilePath, ProcessHandler &&handler, AssociatedLocks &&locks = AssociatedLocks()); std::string &&displayName, std::string &&logFilePath, ProcessHandler &&handler, AssociatedLocks &&locks = AssociatedLocks());
void terminateOngoingBuildProcesses(); void terminateOngoingBuildProcesses();
void streamFile(const WebAPI::Params &params, const std::string &filePath, std::string_view fileMimeType); void streamFile(const WebAPI::Params &params, const std::string &filePath, boost::beast::string_view fileMimeType,
boost::beast::string_view contentDisposition = boost::beast::string_view());
ServiceSetup *setup(); ServiceSetup *setup();
protected: protected:

View File

@ -407,7 +407,8 @@ void BuildAction::terminateOngoingBuildProcesses()
} }
} }
void BuildAction::streamFile(const WebAPI::Params &params, const std::string &filePath, std::string_view fileMimeType) void BuildAction::streamFile(
const WebAPI::Params &params, const std::string &filePath, boost::beast::string_view fileMimeType, boost::beast::string_view contentDisposition)
{ {
auto buildProcess = std::shared_ptr<BuildProcessSession>(); auto buildProcess = std::shared_ptr<BuildProcessSession>();
if (const auto outputLock = std::unique_lock<std::mutex>(m_outputSessionMutex); m_outputSession && m_outputSession->logFilePath() == filePath) { if (const auto outputLock = std::unique_lock<std::mutex>(m_outputSessionMutex); m_outputSession && m_outputSession->logFilePath() == filePath) {
@ -418,12 +419,12 @@ void BuildAction::streamFile(const WebAPI::Params &params, const std::string &fi
} }
if (!buildProcess) { if (!buildProcess) {
// simply send the file if there's no ongoing process writing to it anymore // 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; return;
} }
// stream the output of the ongoing process // 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, boost::beast::http::async_write_header(params.session.socket(), chunkResponse->serializer,
[chunkResponse, filePath, buildProcess, session = params.session.shared_from_this()]( [chunkResponse, filePath, buildProcess, session = params.session.shared_from_this()](
const boost::system::error_code &error, std::size_t) mutable { const boost::system::error_code &error, std::size_t) mutable {

View File

@ -146,7 +146,7 @@ std::shared_ptr<Response> makeServerError(const Request &request, std::string_vi
return res; return res;
} }
std::shared_ptr<Response> makeData(const Request &request, const string &buffer, const char *mimeType) std::shared_ptr<Response> makeData(const Request &request, const string &buffer, boost::beast::string_view mimeType)
{ {
const auto res = make_shared<Response>(http::status::ok, request.version()); const auto res = make_shared<Response>(http::status::ok, request.version());
res->set(http::field::server, BOOST_BEAST_VERSION_STRING); res->set(http::field::server, BOOST_BEAST_VERSION_STRING);
@ -158,7 +158,7 @@ std::shared_ptr<Response> makeData(const Request &request, const string &buffer,
return res; return res;
} }
std::shared_ptr<Response> makeData(const Request &request, string &&buffer, const char *mimeType) std::shared_ptr<Response> makeData(const Request &request, string &&buffer, boost::beast::string_view mimeType)
{ {
const auto res = make_shared<Response>(http::status::ok, request.version()); const auto res = make_shared<Response>(http::status::ok, request.version());
res->set(http::field::server, BOOST_BEAST_VERSION_STRING); res->set(http::field::server, BOOST_BEAST_VERSION_STRING);
@ -170,11 +170,13 @@ std::shared_ptr<Response> makeData(const Request &request, string &&buffer, cons
return res; return res;
} }
std::shared_ptr<Response> makeData(const Request &request, rapidjson::StringBuffer &&buffer, const char *mimeType) std::shared_ptr<Response> makeData(const Request &request, rapidjson::StringBuffer &&buffer, boost::beast::string_view mimeType)
{ {
const auto res = make_shared<Response>(http::status::ok, request.version()); const auto res = make_shared<Response>(http::status::ok, request.version());
res->set(http::field::server, BOOST_BEAST_VERSION_STRING); 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->set(http::field::access_control_allow_origin, "*");
res->keep_alive(request.keep_alive()); res->keep_alive(request.keep_alive());
res->body().assign(buffer.GetString(), buffer.GetSize()); res->body().assign(buffer.GetString(), buffer.GetSize());
@ -182,11 +184,17 @@ std::shared_ptr<Response> makeData(const Request &request, rapidjson::StringBuff
return res; return res;
} }
std::shared_ptr<FileResponse> makeFile(const Request &request, const char *filePath, const char *mimeType, boost::beast::error_code &ec) std::shared_ptr<FileResponse> 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>(); const auto fileResponse = std::make_shared<FileResponse>();
fileResponse->set(http::field::server, BOOST_BEAST_VERSION_STRING); 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->set(http::field::access_control_allow_origin, "*");
fileResponse->keep_alive(request.keep_alive()); fileResponse->keep_alive(request.keep_alive());
fileResponse->body().open(filePath, file_mode::scan, ec); fileResponse->body().open(filePath, file_mode::scan, ec);
@ -202,10 +210,16 @@ inline ChunkResponse::ChunkResponse()
response.chunked(true); response.chunked(true);
} }
std::shared_ptr<ChunkResponse> makeChunkResponse(const Request &request, const char *mimeType) std::shared_ptr<ChunkResponse> makeChunkResponse(
const Request &request, boost::beast::string_view mimeType, boost::beast::string_view contentDisposition)
{ {
const auto chunkResponse = std::make_shared<ChunkResponse>(); const auto chunkResponse = std::make_shared<ChunkResponse>();
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()); chunkResponse->response.keep_alive(request.keep_alive());
return chunkResponse; return chunkResponse;
} }

View File

@ -31,14 +31,16 @@ std::shared_ptr<Response> makeNotFound(const Request &request, std::string_view
std::shared_ptr<Response> makeAuthRequired(const Request &request); std::shared_ptr<Response> makeAuthRequired(const Request &request);
std::shared_ptr<Response> makeForbidden(const Request &request); std::shared_ptr<Response> makeForbidden(const Request &request);
std::shared_ptr<Response> makeServerError(const Request &request, std::string_view what); std::shared_ptr<Response> makeServerError(const Request &request, std::string_view what);
std::shared_ptr<Response> makeData(const Request &request, const std::string &data, const char *mimeType); std::shared_ptr<Response> makeData(const Request &request, const std::string &data, boost::beast::string_view mimeType);
std::shared_ptr<Response> makeData(const Request &request, std::string &&data, const char *mimeType); std::shared_ptr<Response> makeData(const Request &request, std::string &&data, boost::beast::string_view mimeType);
std::shared_ptr<Response> makeData(const Request &request, RAPIDJSON_NAMESPACE::StringBuffer &&buffer, const char *mimeType); std::shared_ptr<Response> makeData(const Request &request, RAPIDJSON_NAMESPACE::StringBuffer &&buffer, boost::beast::string_view mimeType);
std::shared_ptr<Response> makeText(const Request &request, const std::string &text); std::shared_ptr<Response> makeText(const Request &request, const std::string &text);
std::shared_ptr<Response> makeText(const Request &request, std::string &&text); std::shared_ptr<Response> makeText(const Request &request, std::string &&text);
std::shared_ptr<Response> makeJson(const Request &request, std::string &&json); std::shared_ptr<Response> makeJson(const Request &request, std::string &&json);
std::shared_ptr<FileResponse> makeFile(const Request &request, const char *filePath, const char *mimeType, boost::beast::error_code &ec); std::shared_ptr<FileResponse> makeFile(const Request &request, const char *filePath, boost::beast::string_view mimeType,
std::shared_ptr<ChunkResponse> makeChunkResponse(const Request &request, const char *mimeType); boost::beast::string_view contentDisposition, boost::beast::error_code &ec);
std::shared_ptr<ChunkResponse> makeChunkResponse(
const Request &request, boost::beast::string_view mimeType, boost::beast::string_view contentDisposition);
inline std::shared_ptr<Response> makeText(const Request &request, const std::string &text) inline std::shared_ptr<Response> makeText(const Request &request, const std::string &text)
{ {

View File

@ -1,3 +1,5 @@
#define CPP_UTILITIES_PATHHELPER_STRING_VIEW
#include "./params.h" #include "./params.h"
#include "./render.h" #include "./render.h"
#include "./routes.h" #include "./routes.h"
@ -5,6 +7,7 @@
#include "../serversetup.h" #include "../serversetup.h"
#include <c++utilities/conversion/stringbuilder.h> #include <c++utilities/conversion/stringbuilder.h>
#include <c++utilities/io/path.h>
#include <algorithm> #include <algorithm>
#include <variant> #include <variant>
@ -129,9 +132,16 @@ static void getBuildActionFile(
if (name.empty()) { if (name.empty()) {
return; return;
} }
auto mimeType = std::string_view("application/octet-stream"); auto mimeType = boost::beast::string_view("application/octet-stream");
auto contentDisposition = std::string();
if (determineMimeType) { 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); auto buildActionsSearchResult = findBuildActions(params, std::move(handler), false, 1);
if (!buildActionsSearchResult.ok) { if (!buildActionsSearchResult.ok) {
@ -141,7 +151,7 @@ static void getBuildActionFile(
auto &buildAction = buildActionsSearchResult.actions.front(); auto &buildAction = buildActionsSearchResult.actions.front();
for (const auto &logFile : (*buildAction).*fileList) { for (const auto &logFile : (*buildAction).*fileList) {
if (name == logFile) { if (name == logFile) {
buildAction->streamFile(params, name, mimeType); buildAction->streamFile(params, name, mimeType, contentDisposition);
return; return;
} }
} }

View File

@ -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) // 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)) { 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); 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; return;
} }
@ -128,11 +128,12 @@ void Session::respond(std::shared_ptr<Response> &&response)
m_res = move(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 // make response with file body
auto ec = boost::beast::error_code{}; 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()) { if (ec.failed()) {
respond(Render::makeNotFound(m_parser->get(), urlPath)); respond(Render::makeNotFound(m_parser->get(), urlPath));
return; return;
@ -167,7 +168,7 @@ void Session::responded(boost::system::error_code ec, std::size_t bytesTransferr
receive(); 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")) { if (path.ends_with(".html")) {
return "text/html"; return "text/html";

View File

@ -22,14 +22,15 @@ public:
void receive(); void receive();
void respond(std::shared_ptr<Response> &&response); void respond(std::shared_ptr<Response> &&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(); void close();
const Request &request() const; const Request &request() const;
void assignEmptyRequest(); void assignEmptyRequest();
boost::asio::ip::tcp::socket &socket(); boost::asio::ip::tcp::socket &socket();
void received(boost::system::error_code ec, std::size_t bytesTransferred); void received(boost::system::error_code ec, std::size_t bytesTransferred);
void responded(boost::system::error_code ec, std::size_t bytesTransferred, bool shouldClose); 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: private:
boost::asio::ip::tcp::socket m_socket; boost::asio::ip::tcp::socket m_socket;