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::string &&displayName, std::string &&logFilePath, ProcessHandler &&handler, AssociatedLocks &&locks = AssociatedLocks());
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();
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>();
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) {
// 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 {

View File

@ -146,7 +146,7 @@ std::shared_ptr<Response> makeServerError(const Request &request, std::string_vi
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());
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;
}
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());
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;
}
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());
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<Response> makeData(const Request &request, rapidjson::StringBuff
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>();
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<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>();
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;
}

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> makeForbidden(const Request &request);
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, std::string &&data, const char *mimeType);
std::shared_ptr<Response> makeData(const Request &request, RAPIDJSON_NAMESPACE::StringBuffer &&buffer, 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, boost::beast::string_view 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, std::string &&text);
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<ChunkResponse> makeChunkResponse(const Request &request, const char *mimeType);
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);
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)
{

View File

@ -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 <c++utilities/conversion/stringbuilder.h>
#include <c++utilities/io/path.h>
#include <algorithm>
#include <variant>
@ -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;
}
}

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)
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> &&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";

View File

@ -22,14 +22,15 @@ public:
void receive();
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();
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;