feat: implement Xtream proxy handler with M3U8 URL rewriting, SSRF protection, and content type handling.

This commit is contained in:
Michael committed 2026-01-07 11:31:40 +01:00
1 parent 755367018a
commit 758cbe4c43
1 file changed
+24 -14
+24 -14
View File
@@ -88,21 +88,31 @@ class ProxyHandler {
final targetUrl = Uri.parse(fullUrl); final targetUrl = Uri.parse(fullUrl);
// SSRF Protection via Domain Allowlist // SSRF Protection - but allow images/static assets from any host
final playlist = await _getPlaylist(request); // Xtream providers often use separate CDN servers for picons/images
if (playlist == null) { final isStaticAsset = targetUrl.path.endsWith('.png') ||
return Response.forbidden('No active playlist configuration found to validate request'); targetUrl.path.endsWith('.jpg') ||
} targetUrl.path.endsWith('.jpeg') ||
targetUrl.path.endsWith('.gif') ||
// Normalize domains for comparison targetUrl.path.endsWith('.webp') ||
final targetHost = targetUrl.host.toLowerCase(); targetUrl.path.endsWith('.ico') ||
final allowedHost = Uri.parse(playlist.dns).host.toLowerCase(); targetUrl.path.contains('/picons/') ||
targetUrl.path.contains('/logos/');
bool isAllowed = targetHost == allowedHost; if (!isStaticAsset) {
// For API calls, enforce domain allowlist
if (!isAllowed) { final playlist = await _getPlaylist(request);
print('[Proxy] Blocked SSRF attempt to $targetHost (Allowed: $allowedHost)'); if (playlist == null) {
return Response.forbidden('Access to this domain is forbidden by policy'); return Response.forbidden('No active playlist configuration found to validate request');
}
final targetHost = targetUrl.host.toLowerCase();
final allowedHost = Uri.parse(playlist.dns).host.toLowerCase();
if (targetHost != allowedHost) {
print('[Proxy] Blocked SSRF attempt to $targetHost (Allowed: $allowedHost)');
return Response.forbidden('Access to this domain is forbidden by policy');
}
} }
final proxyHeaders = { final proxyHeaders = {