Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/linux-system.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions src/protocol/amcp/AMCPCommandsImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"); }
Expand Down
40 changes: 25 additions & 15 deletions src/protocol/util/http_request.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#include "http_request.h"

#include <common/except.h>
#include <common/log.h>

#include <boost/asio.hpp>
#include <iomanip>
#include <sstream>
#include <string>

Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
4 changes: 3 additions & 1 deletion src/protocol/util/http_request.h
Original file line number Diff line number Diff line change
Expand Up @@ -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