diff --git a/.changes/stalker-mpv-header-comma-escape.md b/.changes/stalker-mpv-header-comma-escape.md new file mode 100644 index 000000000..e6612cd18 --- /dev/null +++ b/.changes/stalker-mpv-header-comma-escape.md @@ -0,0 +1,10 @@ +--- +type: fix +area: playback +--- + +Stalker live TV plays again in the embedded MPV player. The MAG250 user agent +contains a comma (`KHTML, like Gecko`), which MPV splits in its comma-separated +`--http-header-fields` list, truncating `X-User-Agent` so strict portals reject +the stream with HTTP 400. Commas and backslashes in header values are now +escaped on the Windows/Linux and macOS embedded-MPV paths. diff --git a/apps/electron-backend/native/src/embedded_mpv.mm b/apps/electron-backend/native/src/embedded_mpv.mm index 76cf6cf2c..54e7730d2 100644 --- a/apps/electron-backend/native/src/embedded_mpv.mm +++ b/apps/electron-backend/native/src/embedded_mpv.mm @@ -766,6 +766,19 @@ void onRenderContextUpdate(void* context) } } +void replaceAllInPlace(std::string& value, const std::string& from, const std::string& to) +{ + if (from.empty()) { + return; + } + + size_t position = 0; + while ((position = value.find(from, position)) != std::string::npos) { + value.replace(position, from.length(), to); + position += to.length(); + } +} + std::string joinHeaderFields(const Napi::Object& headers) { const Napi::Array propertyNames = headers.GetPropertyNames(); @@ -789,6 +802,15 @@ std::string joinHeaderFields(const Napi::Object& headers) continue; } + // mpv's `--http-header-fields` option is a comma-separated list + // (OPT_STRINGLIST). Header values that themselves contain commas + // (e.g. the Stalker MAG250 user agent "...(KHTML, like Gecko) MAG250") + // must be escaped so mpv does not split them into bogus fields and + // the server rejects the request with HTTP 400. mpv strips the + // backslash and keeps the comma inside the header value. + replaceAllInPlace(value, "\\", "\\\\"); + replaceAllInPlace(value, ",", "\\,"); + fields.push_back(key + ": " + value); } diff --git a/apps/electron-backend/native/src/embedded_mpv_wid_common.h b/apps/electron-backend/native/src/embedded_mpv_wid_common.h index 814e1153a..507d62305 100644 --- a/apps/electron-backend/native/src/embedded_mpv_wid_common.h +++ b/apps/electron-backend/native/src/embedded_mpv_wid_common.h @@ -289,6 +289,19 @@ std::string normalizeEnvValue(const char* value) return result; } +void replaceAllInPlace(std::string& value, const std::string& from, const std::string& to) +{ + if (from.empty()) { + return; + } + + size_t position = 0; + while ((position = value.find(from, position)) != std::string::npos) { + value.replace(position, from.length(), to); + position += to.length(); + } +} + std::string joinHeaderFields(const Napi::Object& headers) { const Napi::Array propertyNames = headers.GetPropertyNames(); @@ -312,6 +325,15 @@ std::string joinHeaderFields(const Napi::Object& headers) continue; } + // mpv's `--http-header-fields` option is a comma-separated list + // (OPT_STRINGLIST). Header values that themselves contain commas + // (e.g. the Stalker MAG250 user agent "...(KHTML, like Gecko) MAG250") + // must be escaped so mpv does not split them into bogus fields and + // the server rejects the request with HTTP 400. mpv strips the + // backslash and keeps the comma inside the header value. + replaceAllInPlace(value, "\\", "\\\\"); + replaceAllInPlace(value, ",", "\\,"); + fields.push_back(key + ": " + value); }