fix: harden M3U8 ad filtering

Reject unsafe dynamic filter execution, reduce heuristic false positives, add regression coverage, bump the app to 4.9.17, and update Next.js to the latest stable security patch.
This commit is contained in:
kuekhaoyang
2026-07-29 22:13:54 +08:00
parent c53801d3bf
commit cdab3b1c8a
18 changed files with 620 additions and 754 deletions
+6 -6
View File
@@ -22,7 +22,7 @@ export function useHlsPlayer({
onError
}: UseHlsPlayerProps) {
const hlsRef = useRef<Hls | null>(null);
const { adFilterMode, adKeywords, customAdFilterCode } = usePlayerSettings(isPremium);
const { adFilterMode, adKeywords } = usePlayerSettings(isPremium);
const { mediaProxyEnabled } = useRuntimeFeatures();
const isAdFilterEnabled = adFilterMode !== 'off';
@@ -59,7 +59,7 @@ export function useHlsPlayer({
if (typeof response.data === 'string') {
try {
// Filter the content
response.data = filterM3u8Ad(response.data, context.url, adFilterMode, adKeywords, customAdFilterCode);
response.data = filterM3u8Ad(response.data, context.url, adFilterMode, adKeywords);
} catch (e) {
console.warn('[HLS] Ad filter error:', e);
}
@@ -255,7 +255,7 @@ export function useHlsPlayer({
// If it's a simple playlist (no variants), just filter and play
if (!masterContent.includes('#EXT-X-STREAM-INF')) {
const filtered = filterM3u8Ad(masterContent, absoluteMasterSrc, adFilterMode, adKeywords, customAdFilterCode);
const filtered = filterM3u8Ad(masterContent, absoluteMasterSrc, adFilterMode, adKeywords);
const blob = new Blob([filtered], { type: 'application/vnd.apple.mpegurl' });
const blobUrl = URL.createObjectURL(blob);
createdBlobs.push(blobUrl);
@@ -283,7 +283,7 @@ export function useHlsPlayer({
try {
const absoluteUrl = isRelative ? new URL(uri, absoluteMasterSrc).toString() : uri;
const subContent = await fetchWithFallback(absoluteUrl);
const filteredSub = filterM3u8Ad(subContent, absoluteUrl, adFilterMode, adKeywords, customAdFilterCode);
const filteredSub = filterM3u8Ad(subContent, absoluteUrl, adFilterMode, adKeywords);
const subBlob = new Blob([filteredSub], { type: 'application/vnd.apple.mpegurl' });
const subBlobUrl = URL.createObjectURL(subBlob);
createdBlobs.push(subBlobUrl);
@@ -306,7 +306,7 @@ export function useHlsPlayer({
try {
const absoluteUrl = isRelative ? new URL(trimmedLine, absoluteMasterSrc).toString() : trimmedLine;
const subContent = await fetchWithFallback(absoluteUrl);
const filteredSub = filterM3u8Ad(subContent, absoluteUrl, adFilterMode, adKeywords, customAdFilterCode);
const filteredSub = filterM3u8Ad(subContent, absoluteUrl, adFilterMode, adKeywords);
const subBlob = new Blob([filteredSub], { type: 'application/vnd.apple.mpegurl' });
const subBlobUrl = URL.createObjectURL(subBlob);
createdBlobs.push(subBlobUrl);
@@ -424,5 +424,5 @@ export function useHlsPlayer({
}
extraBlobs.forEach(url => URL.revokeObjectURL(url));
};
}, [src, videoRef, autoPlay, onAutoPlayPrevented, onError, isAdFilterEnabled, adFilterMode, adKeywords, customAdFilterCode, mediaProxyEnabled]);
}, [src, videoRef, autoPlay, onAutoPlayPrevented, onError, isAdFilterEnabled, adFilterMode, adKeywords, mediaProxyEnabled]);
}
@@ -22,8 +22,6 @@ interface PlayerSettingsSnapshot {
adFilter: boolean;
adFilterMode: AdFilterMode;
adKeywords: string[];
customAdFilterCode: string;
customAdFilterVersion: number;
fullscreenType: 'auto' | 'native' | 'window';
proxyMode: 'retry' | 'none' | 'always';
danmakuEnabled: boolean;
@@ -47,8 +45,6 @@ function getPlayerSettingsSnapshot(isPremium: boolean, mediaProxyEnabled: boolea
adFilter: globalSettings.adFilter,
adFilterMode: modeSettings.adFilterMode,
adKeywords: globalSettings.adKeywords,
customAdFilterCode: globalSettings.customAdFilterCode || '',
customAdFilterVersion: globalSettings.customAdFilterVersion || 0,
fullscreenType: modeSettings.fullscreenType,
proxyMode: mediaProxyEnabled ? modeSettings.proxyMode : 'none',
danmakuEnabled: modeSettings.danmakuEnabled,
@@ -70,8 +66,6 @@ function playerSettingsEqual(a: PlayerSettingsSnapshot, b: PlayerSettingsSnapsho
a.adFilter === b.adFilter &&
a.adFilterMode === b.adFilterMode &&
a.adKeywords === b.adKeywords &&
a.customAdFilterCode === b.customAdFilterCode &&
a.customAdFilterVersion === b.customAdFilterVersion &&
a.fullscreenType === b.fullscreenType &&
a.proxyMode === b.proxyMode &&
a.danmakuEnabled === b.danmakuEnabled &&
@@ -171,13 +165,6 @@ export function usePlayerSettings(isPremium: boolean = false) {
updateGlobalSettings({ adKeywords: value });
}, [updateGlobalSettings]);
const setCustomAdFilterCode = useCallback((code: string, version?: number) => {
updateGlobalSettings({
customAdFilterCode: code,
...(version !== undefined ? { customAdFilterVersion: version } : {})
});
}, [updateGlobalSettings]);
const setFullscreenType = useCallback((value: 'auto' | 'native' | 'window') => {
updateModeSettings({ fullscreenType: value });
}, [updateModeSettings]);
@@ -217,7 +204,6 @@ export function usePlayerSettings(isPremium: boolean = false) {
setAdFilter,
setAdFilterMode,
setAdKeywords,
setCustomAdFilterCode,
setFullscreenType,
setProxyMode,
setDanmakuEnabled,