Skip to content

Commit c4ef5fd

Browse files
authored
fix(http-proxy): preserve m3u redirect semantics (#681)
## Summary - restrict M3U response body rewriting to successful 2xx responses - preserve normal HTTP redirect handling for M3U and M3U8 request paths - add an end-to-end regression test covering a 302 M3U8 response with an HTML body ## Root cause M3U detection by URL extension ran for every response status. A 302 response from a path ending in .m3u8 therefore entered the playlist body rewrite path, which bypassed the existing Location header rewrite and treated the redirect body as playlist content. Clients could then be redirected directly to an IPTV-only upstream address that they could not access. ## Impact Redirects and error responses from M3U endpoints now retain normal HTTP semantics. Successful playlist responses continue to be decoded and rewritten as before. ## Validation - Release CMake build - pnpm run lint:clang - uv run ruff check e2e - ./scripts/run-e2e.sh -p 1 test_http_proxy_m3u_rewrite.py — 52 passed - ./scripts/run-e2e.sh -p 1 test_http_proxy.py — 19 passed - ./scripts/run-e2e.sh -p 1 test_url_template_m3u.py — 19 passed - uv run pytest e2e --collect-only -q — 540 tests collected - ./scripts/run-e2e.sh --co — 540 tests collected - git diff --check
1 parent eb82ba9 commit c4ef5fd

2 files changed

Lines changed: 36 additions & 3 deletions

File tree

‎e2e/test_http_proxy_m3u_rewrite.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,36 @@ def test_content_type_with_charset(self, shared_r2h):
688688
finally:
689689
upstream.stop()
690690

691+
def test_m3u_url_redirect_keeps_http_redirect_semantics(self, shared_r2h):
692+
"""A redirect from an M3U URL should rewrite Location, not its HTML body."""
693+
redirect_body = b"<html>redirecting</html>\n"
694+
upstream = MockHTTPUpstream(
695+
routes={
696+
"/archive/index.m3u8": {
697+
"status": 302,
698+
"body": redirect_body,
699+
"headers": {
700+
"Content-Type": "text/html",
701+
"Location": "http://10.0.0.1:8080/final/index.m3u8",
702+
},
703+
},
704+
}
705+
)
706+
upstream.start()
707+
try:
708+
status, hdrs, body = http_get(
709+
"127.0.0.1",
710+
shared_r2h.port,
711+
f"/http/127.0.0.1:{upstream.port}/archive/index.m3u8",
712+
timeout=_TIMEOUT,
713+
)
714+
715+
assert status == 302
716+
assert get_header(hdrs, "Location") == "/http/10.0.0.1:8080/final/index.m3u8"
717+
assert body == redirect_body
718+
finally:
719+
upstream.stop()
720+
691721

692722
# ---------------------------------------------------------------------------
693723
# Complex / realistic playlists

‎src/http_proxy.c‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,14 +1133,17 @@ static int http_proxy_parse_response_headers(http_proxy_session_t *session) {
11331133

11341134
session->headers_received = 1;
11351135

1136-
/* Check if response body needs rewriting (M3U content). URL extension takes
1137-
* precedence; fall back to Content-Type only when the URL is not M3U-like.
1136+
/* Check if a successful response body needs rewriting (M3U content). URL
1137+
* extension takes precedence; fall back to Content-Type only when the URL
1138+
* is not M3U-like. Non-2xx responses must retain normal HTTP semantics,
1139+
* especially redirect Location rewriting and error response passthrough.
11381140
* Skip for HEAD requests — there is no body to rewrite. */
11391141
int is_m3u_response = rewrite_is_m3u_url(session->target_path);
11401142
if (!is_m3u_response)
11411143
is_m3u_response = rewrite_is_m3u_content_type(session->response_content_type);
11421144

1143-
if (is_m3u_response && strcasecmp(session->method, "HEAD") != 0) {
1145+
if (session->response_status_code >= 200 && session->response_status_code < 300 && is_m3u_response &&
1146+
strcasecmp(session->method, "HEAD") != 0) {
11441147
if (session->transfer_encoding_seen && (!session->response_is_chunked || session->unsupported_transfer_coding)) {
11451148
logger(LOG_ERROR, "HTTP Proxy: Unsupported Transfer-Encoding for M3U rewrite");
11461149
return -1;

0 commit comments

Comments
 (0)