releases/VideoDownloader.Browser/PermissionGate.cs

78 lines
3.4 KiB
C#

namespace VideoDownloader.Browser;
public enum PermissionVerdict { Ask, Allow, Deny }
/// <summary>
/// T3-11 사이트별 권한 정책 — 출처×종류 단위 기억. WebView2 PermissionRequested 에서
/// Ask 만 프롬프트로 올리고 Allow/Deny 는 즉시 응답. "항상" 선택은 네이티브
/// Profile.SetPermissionStateAsync 로도 영속(세션 밖 저장).
/// </summary>
public sealed class PermissionGate
{
/// <summary>프롬프트 없이 거절: 센서류(피싱 표면 축소).</summary>
private static readonly HashSet<string> AutoDeny = new(StringComparer.OrdinalIgnoreCase) { "OtherSensors" };
/// <summary>프롬프트 없이 허용: 연속 다운로드(다운로더 앱 본업).</summary>
private static readonly HashSet<string> AutoAllow = new(StringComparer.OrdinalIgnoreCase) { "MultipleAutomaticDownloads" };
private readonly Dictionary<(string origin, string kind), PermissionVerdict> _saved = new();
public PermissionVerdict Decide(string kind, string origin)
{
if (AutoDeny.Contains(kind)) return PermissionVerdict.Deny;
if (AutoAllow.Contains(kind)) return PermissionVerdict.Allow;
return _saved.GetValueOrDefault((origin, kind), PermissionVerdict.Ask);
}
public void Remember(string kind, string origin, PermissionVerdict verdict)
=> _saved[(origin, kind)] = verdict;
}
/// <summary>T3-14: 추적 스크립트 블랙리스트 — 호스트 또는 서브도메인 매칭.</summary>
public sealed class TrackerBlocklist
{
/// <summary>주요 광고/분석 도메인(EasyList 공통 항목 발췌). 생성자 주입으로 교체 가능(테스트).</summary>
public static readonly string[] DefaultDomains =
{
"google-analytics.com", "googletagmanager.com", "doubleclick.net", "googlesyndication.com",
"googleadservices.com", "scorecardresearch.com", "hotjar.com", "mixpanel.com", "segment.io",
"facebook.net", "connect.facebook.net", "amplitude.com", "branch.io", "appsflyer.com",
"criteo.com", "taboola.com", "outbrain.com", "adnxs.com", "pubmatic.com", "rubiconproject.com",
};
private readonly HashSet<string> _domains;
public TrackerBlocklist() : this(DefaultDomains) { }
public TrackerBlocklist(IEnumerable<string> domains) =>
_domains = new HashSet<string>(domains, StringComparer.OrdinalIgnoreCase);
public bool Matches(Uri url)
{
var host = url.Host;
foreach (var d in _domains)
if (host.Equals(d, StringComparison.OrdinalIgnoreCase) ||
host.EndsWith("." + d, StringComparison.OrdinalIgnoreCase))
return true;
return false;
}
}
/// <summary>T3-14: 차단 카운터 — 총계/도메인별 집계, 변경 통지(실드 배지 갱신용).</summary>
public sealed class TrackerCounter
{
private int _total;
public int Total => Volatile.Read(ref _total);
public Dictionary<string, int> ByDomain { get; } = new(StringComparer.OrdinalIgnoreCase);
public event Action? Changed;
public void Record(string url)
{
var host = new Uri(url).Host;
var baseDomain = host.StartsWith("www.", StringComparison.OrdinalIgnoreCase) ? host[4..] : host;
Interlocked.Increment(ref _total);
lock (ByDomain)
{
ByDomain[baseDomain] = ByDomain.GetValueOrDefault(baseDomain) + 1;
}
Changed?.Invoke();
}
}