diff --git a/.github/workflows/linux-system.yml b/.github/workflows/linux-system.yml index 8ae80eafc0..91fe44b1c2 100644 --- a/.github/workflows/linux-system.yml +++ b/.github/workflows/linux-system.yml @@ -27,7 +27,7 @@ jobs: # Build a version number for this build GH_REF="${GITHUB_REF##*/}" - GH_REF=$(echo "$GH_REF" | sed 's/[\/]/_/g' | sed 's/ /_/g') + GH_REF=$(echo "$GH_REF" | sed 's/[\/]/-/g' | sed 's/ /-/g' | sed 's/_/-/g') VERSION_MAJOR=$(grep -oPi 'set\(CONFIG_VERSION_MAJOR \K\d+' src/CMakeLists.txt) VERSION_MINOR=$(grep -oPi 'set\(CONFIG_VERSION_MINOR \K\d+' src/CMakeLists.txt) diff --git a/src/protocol/amcp/AMCPCommandsImpl.cpp b/src/protocol/amcp/AMCPCommandsImpl.cpp index 96bb56fac4..741e52ef10 100644 --- a/src/protocol/amcp/AMCPCommandsImpl.cpp +++ b/src/protocol/amcp/AMCPCommandsImpl.cpp @@ -1463,13 +1463,13 @@ std::wstring thumbnail_list_command(command_context& ctx) std::wstring thumbnail_retrieve_command(command_context& ctx) { return make_request( - ctx, "/thumbnail/" + http::url_encode(u8(ctx.parameters.at(0))), L"501 THUMBNAIL RETRIEVE FAILED\r\n"); + ctx, "/thumbnail/" + http::url_encode_path(u8(ctx.parameters.at(0))), L"501 THUMBNAIL RETRIEVE FAILED\r\n"); } std::wstring thumbnail_generate_command(command_context& ctx) { return make_request( - ctx, "/thumbnail/generate/" + http::url_encode(u8(ctx.parameters.at(0))), L"501 THUMBNAIL GENERATE FAILED\r\n"); + ctx, "/thumbnail/generate/" + http::url_encode_path(u8(ctx.parameters.at(0))), L"501 THUMBNAIL GENERATE FAILED\r\n"); } std::wstring thumbnail_generateall_command(command_context& ctx) @@ -1481,7 +1481,7 @@ std::wstring thumbnail_generateall_command(command_context& ctx) std::wstring cinf_command(command_context& ctx) { - return make_request(ctx, "/cinf/" + http::url_encode(u8(ctx.parameters.at(0))), L"501 CINF FAILED\r\n"); + return make_request(ctx, "/cinf/" + http::url_encode_path(u8(ctx.parameters.at(0))), L"501 CINF FAILED\r\n"); } std::wstring cls_command(command_context& ctx) { return make_request(ctx, "/cls", L"501 CLS FAILED\r\n"); } diff --git a/src/protocol/util/http_request.cpp b/src/protocol/util/http_request.cpp index 50275dd8a4..3269eb3d06 100644 --- a/src/protocol/util/http_request.cpp +++ b/src/protocol/util/http_request.cpp @@ -1,9 +1,9 @@ #include "http_request.h" #include +#include #include -#include #include #include @@ -16,6 +16,9 @@ HTTPResponse request(const std::string& host, const std::string& port, const std HTTPResponse res; + // Log the URL being requested for debugging + CASPAR_LOG(info) << "HTTP GET: " << path; + asio::io_context io_context; // Get a list of endpoints corresponding to the server name. @@ -63,13 +66,11 @@ HTTPResponse request(const std::string& host, const std::string& port, const std std::getline(response_stream, res.status_message); if (!response_stream || http_version.substr(0, 5) != "HTTP/") { - // TODO - CASPAR_THROW_EXCEPTION(io_error() << msg_info("Invalid Response")); + CASPAR_THROW_EXCEPTION(io_error() << msg_info("Invalid HTTP response")); } if (res.status_code < 200 || res.status_code >= 300) { - // TODO - CASPAR_THROW_EXCEPTION(io_error() << msg_info("Invalid Response")); + CASPAR_THROW_EXCEPTION(io_error() << msg_info("HTTP request failed with status " + std::to_string(res.status_code))); } // Read the response headers, which are terminated by a blank line. @@ -102,21 +103,30 @@ HTTPResponse request(const std::string& host, const std::string& port, const std return res; } -std::string url_encode(const std::string& str) +// URL-encode a file path, preserving '/' and '\' as path separators. +// Encodes special characters in each path component per RFC 3986. +// Only unreserved characters (A-Za-z0-9-_.~) are not encoded. +std::string url_encode_path(const std::string& path) { - std::stringstream escaped; - escaped.fill('0'); - escaped << std::hex; - - for (auto c : str) { - if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { - escaped << c; + std::string result; + result.reserve(path.size() * 2); // Reserve space to avoid reallocations + + for (auto c : path) { + // Treat both forward slash and backslash as path separators + // Always output forward slash for HTTP URLs + if (c == '/' || c == '\\') { + result += '/'; + } else if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { + result += c; } else { - escaped << std::uppercase << '%' << std::setw(2) << int((unsigned char)c) << std::nouppercase; + // Encode special character as %XX + result += '%'; + result += "0123456789ABCDEF"[(unsigned char)c >> 4]; + result += "0123456789ABCDEF"[(unsigned char)c & 0x0F]; } } - return escaped.str(); + return result; } }} // namespace caspar::http diff --git a/src/protocol/util/http_request.h b/src/protocol/util/http_request.h index 53263fd95f..4f17640018 100644 --- a/src/protocol/util/http_request.h +++ b/src/protocol/util/http_request.h @@ -15,6 +15,8 @@ struct HTTPResponse HTTPResponse request(const std::string& host, const std::string& port, const std::string& path); -std::string url_encode(const std::string& str); +// URL-encode a file path, preserving '/' and '\' as path separators. +// Encodes special characters in each path component per RFC 3986. +std::string url_encode_path(const std::string& path); }} // namespace caspar::http