Skip to content

Commit 0d53f1b

Browse files
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.
1 parent 0a6b136 commit 0d53f1b

File tree

3 files changed

+27
-19
lines changed

3 files changed

+27
-19
lines changed

src/protocol/amcp/AMCPCommandsImpl.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,13 +1463,13 @@ std::wstring thumbnail_list_command(command_context& ctx)
14631463
std::wstring thumbnail_retrieve_command(command_context& ctx)
14641464
{
14651465
return make_request(
1466-
ctx, "/thumbnail/" + http::url_encode(u8(ctx.parameters.at(0))), L"501 THUMBNAIL RETRIEVE FAILED\r\n");
1466+
ctx, "/thumbnail/" + http::url_encode_path(u8(ctx.parameters.at(0))), L"501 THUMBNAIL RETRIEVE FAILED\r\n");
14671467
}
14681468

14691469
std::wstring thumbnail_generate_command(command_context& ctx)
14701470
{
14711471
return make_request(
1472-
ctx, "/thumbnail/generate/" + http::url_encode(u8(ctx.parameters.at(0))), L"501 THUMBNAIL GENERATE FAILED\r\n");
1472+
ctx, "/thumbnail/generate/" + http::url_encode_path(u8(ctx.parameters.at(0))), L"501 THUMBNAIL GENERATE FAILED\r\n");
14731473
}
14741474

14751475
std::wstring thumbnail_generateall_command(command_context& ctx)
@@ -1481,7 +1481,7 @@ std::wstring thumbnail_generateall_command(command_context& ctx)
14811481

14821482
std::wstring cinf_command(command_context& ctx)
14831483
{
1484-
return make_request(ctx, "/cinf/" + http::url_encode(u8(ctx.parameters.at(0))), L"501 CINF FAILED\r\n");
1484+
return make_request(ctx, "/cinf/" + http::url_encode_path(u8(ctx.parameters.at(0))), L"501 CINF FAILED\r\n");
14851485
}
14861486

14871487
std::wstring cls_command(command_context& ctx) { return make_request(ctx, "/cls", L"501 CLS FAILED\r\n"); }

src/protocol/util/http_request.cpp

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#include <common/except.h>
44

55
#include <boost/asio.hpp>
6-
#include <iomanip>
76
#include <sstream>
87
#include <string>
98

@@ -63,13 +62,11 @@ HTTPResponse request(const std::string& host, const std::string& port, const std
6362
std::getline(response_stream, res.status_message);
6463

6564
if (!response_stream || http_version.substr(0, 5) != "HTTP/") {
66-
// TODO
67-
CASPAR_THROW_EXCEPTION(io_error() << msg_info("Invalid Response"));
65+
CASPAR_THROW_EXCEPTION(io_error() << msg_info("Invalid HTTP response"));
6866
}
6967

7068
if (res.status_code < 200 || res.status_code >= 300) {
71-
// TODO
72-
CASPAR_THROW_EXCEPTION(io_error() << msg_info("Invalid Response"));
69+
CASPAR_THROW_EXCEPTION(io_error() << msg_info("HTTP request failed with status " + std::to_string(res.status_code)));
7370
}
7471

7572
// 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
10299
return res;
103100
}
104101

105-
std::string url_encode(const std::string& str)
102+
// URL-encode a file path, preserving '/' and '\' as path separators.
103+
// Encodes special characters in each path component per RFC 3986.
104+
// Only unreserved characters (A-Za-z0-9-_.~) are not encoded.
105+
std::string url_encode_path(const std::string& path)
106106
{
107-
std::stringstream escaped;
108-
escaped.fill('0');
109-
escaped << std::hex;
110-
111-
for (auto c : str) {
112-
if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
113-
escaped << c;
107+
std::string result;
108+
result.reserve(path.size() * 2); // Reserve space to avoid reallocations
109+
110+
for (auto c : path) {
111+
// Treat both forward slash and backslash as path separators
112+
// Always output forward slash for HTTP URLs
113+
if (c == '/' || c == '\\') {
114+
result += '/';
115+
} else if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
116+
result += c;
114117
} else {
115-
escaped << std::uppercase << '%' << std::setw(2) << int((unsigned char)c) << std::nouppercase;
118+
// Encode special character as %XX
119+
result += '%';
120+
result += "0123456789ABCDEF"[(unsigned char)c >> 4];
121+
result += "0123456789ABCDEF"[(unsigned char)c & 0x0F];
116122
}
117123
}
118124

119-
return escaped.str();
125+
return result;
120126
}
121127

122128
}} // namespace caspar::http

src/protocol/util/http_request.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ struct HTTPResponse
1515

1616
HTTPResponse request(const std::string& host, const std::string& port, const std::string& path);
1717

18-
std::string url_encode(const std::string& str);
18+
// URL-encode a file path, preserving '/' and '\' as path separators.
19+
// Encodes special characters in each path component per RFC 3986.
20+
std::string url_encode_path(const std::string& path);
1921

2022
}} // namespace caspar::http

0 commit comments

Comments
 (0)