From 0d53f1bfc07e2b170ae7acfc8f13ca4545184ebf Mon Sep 17 00:00:00 2001 From: zcybercomputing Date: Sat, 17 Jan 2026 01:05:45 +0000 Subject: [PATCH 1/4] Fix URL encoding for file paths with spaces and special characters - Add url_encode_path() function to handle file paths properly - Encode path components while preserving forward slash separators - Support both Windows backslashes and Unix forward slashes - Update AMCP commands: thumbnail_retrieve, thumbnail_generate, cinf - Improve HTTP error messages with actual status codes - Replace stringstream with direct string operations for better performance - Follow RFC 3986 for URL encoding of unreserved characters Fixes issue where file paths with spaces caused 404 errors in thumbnail retrieval and other media-scanner HTTP requests. --- src/protocol/amcp/AMCPCommandsImpl.cpp | 6 ++--- src/protocol/util/http_request.cpp | 36 +++++++++++++++----------- src/protocol/util/http_request.h | 4 ++- 3 files changed, 27 insertions(+), 19 deletions(-) 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..5db8c20fc9 100644 --- a/src/protocol/util/http_request.cpp +++ b/src/protocol/util/http_request.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include @@ -63,13 +62,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 +99,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 From 194a88663f261043429d14df2ad055961da5a373 Mon Sep 17 00:00:00 2001 From: zcybercomputing Date: Sat, 17 Jan 2026 01:15:48 +0000 Subject: [PATCH 2/4] Add debug logging to see actual HTTP request paths being sent --- src/protocol/util/http_request.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/protocol/util/http_request.cpp b/src/protocol/util/http_request.cpp index 5db8c20fc9..e445bc9aad 100644 --- a/src/protocol/util/http_request.cpp +++ b/src/protocol/util/http_request.cpp @@ -1,6 +1,7 @@ #include "http_request.h" #include +#include #include #include @@ -15,6 +16,9 @@ HTTPResponse request(const std::string& host, const std::string& port, const std HTTPResponse res; + // Debug log the URL being requested + CASPAR_LOG(debug) << "HTTP request: GET " << path; + asio::io_context io_context; // Get a list of endpoints corresponding to the server name. From bed52e5ebd8db4a22f70e90d372b77d87d13494f Mon Sep 17 00:00:00 2001 From: zcybercomputing Date: Sat, 17 Jan 2026 01:23:02 +0000 Subject: [PATCH 3/4] Change HTTP request logging to info level for visibility --- src/protocol/util/http_request.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/protocol/util/http_request.cpp b/src/protocol/util/http_request.cpp index e445bc9aad..3269eb3d06 100644 --- a/src/protocol/util/http_request.cpp +++ b/src/protocol/util/http_request.cpp @@ -16,8 +16,8 @@ HTTPResponse request(const std::string& host, const std::string& port, const std HTTPResponse res; - // Debug log the URL being requested - CASPAR_LOG(debug) << "HTTP request: GET " << path; + // Log the URL being requested for debugging + CASPAR_LOG(info) << "HTTP GET: " << path; asio::io_context io_context; From e6b452cbf8cb0f49c926102fc444c828efc0d0f4 Mon Sep 17 00:00:00 2001 From: zcybercomputing Date: Sun, 25 Jan 2026 00:15:35 +0000 Subject: [PATCH 4/4] fix: Replace underscores with hyphens in Debian package version Debian package versions don't allow underscores. Update the workflow to replace underscores (along with slashes and spaces) with hyphens when generating the build version from the branch name. --- .github/workflows/linux-system.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)