修复:PAC 规则解析与关键域名分流

This commit is contained in:
RoyirinRosmontis
2026-09-16 17:46:54 +08:00
parent 110a526fc6
commit 9376e35025
+66 -11
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
import sys, os, subprocess, time, ctypes, threading, functools, base64, bisect
import sys, os, subprocess, time, ctypes, threading, functools, base64, bisect, re
from pathlib import Path
from PyQt5.QtWidgets import (
@@ -832,8 +832,17 @@ def _is_private_ip(host: str) -> bool:
_CN_RANGES = None # 合并排序后的 (start, end) 整数段
_CN_RANGES_LOADED = False
_FORCE_PROXY_SUFFIXES = None
_FORCE_PROXY_EXACT = None
_FORCE_PROXY_REGEXES = None
_FORCE_PROXY_LOADED = False
# PAC 数据由 CDN 独立下发;此处保留登录/AI 服务的关键后缀兜底。
# 即使远端表暂时未更新或不可读,这些域名也不能因解析到中国 CDN IP 而直连。
_CORE_FORCE_PROXY_SUFFIXES = frozenset((
"anthropic.com", "chatgpt.com", "claude.ai", "googlecdn.com",
"oaistatic.com", "oaiusercontent.com", "openai.com",
))
def _data_path(name: str):
"""数据文件路径:只取打包内置目录(config.json 随 exe 内置)。"""
p = Path(BASE_DIR) / name
@@ -1318,8 +1327,13 @@ def _load_cn_ranges():
return _CN_RANGES
def _load_force_proxy_suffixes():
"""加载 pac_domains.json:命中这些域名后缀的连接强制走隧道(优先于 IP 判定)。"""
global _FORCE_PROXY_SUFFIXES, _FORCE_PROXY_LOADED
"""加载 pac_domains.json 的后缀、精确和正则规则。
domain-list-community 的三种规则语义:普通域名匹配自身及子域;
``full:`` 仅匹配该主机;``regexp:`` 按正则匹配。旧逻辑把后两类
当成带冒号的普通后缀,导致它们永远不命中。
"""
global _FORCE_PROXY_SUFFIXES, _FORCE_PROXY_EXACT, _FORCE_PROXY_REGEXES, _FORCE_PROXY_LOADED
if _FORCE_PROXY_LOADED:
return _FORCE_PROXY_SUFFIXES
_FORCE_PROXY_LOADED = True
@@ -1327,14 +1341,51 @@ def _load_force_proxy_suffixes():
import json
path = _user_path("pac_domains.json")
if path is None:
return _FORCE_PROXY_SUFFIXES # 可选文件,缺失不影响 IP 分流
_FORCE_PROXY_SUFFIXES = sorted(_CORE_FORCE_PROXY_SUFFIXES)
_FORCE_PROXY_EXACT = frozenset()
_FORCE_PROXY_REGEXES = ()
log(f"未找到 pac_domains.json,使用 {len(_FORCE_PROXY_SUFFIXES)} 条关键域名兜底")
return _FORCE_PROXY_SUFFIXES
data = json.loads(Path(path).read_text("utf-8"))
suffixes = sorted({str(d).strip().lower().lstrip("*.")
for d in (data.get("domains") or []) if str(d).strip()})
suffixes = set(_CORE_FORCE_PROXY_SUFFIXES)
exact = set()
regexes = []
invalid_regexes = 0
for raw in data.get("domains") or []:
raw_rule = str(raw).strip()
rule = raw_rule.lower()
if not raw_rule:
continue
if rule.startswith("full:"):
host = rule[len("full:"):].rstrip(".")
if host:
exact.add(host)
continue
if rule.startswith("regexp:"):
# 正则本身可能含大写字符类或大小写敏感字面量;只忽略匹配时的
# 域名大小写,不能在编译前改写 pattern。
pattern = raw_rule[len("regexp:"):]
if not pattern:
invalid_regexes += 1
continue
try:
regexes.append(re.compile(pattern, re.IGNORECASE))
except re.error:
invalid_regexes += 1
continue
suffixes.add(rule.lstrip("*.").rstrip("."))
suffixes.discard("")
_FORCE_PROXY_SUFFIXES = suffixes
log(f"已加载强制代理域名: {len(suffixes)} 条 (pac_domains.json)")
_FORCE_PROXY_EXACT = frozenset(exact)
_FORCE_PROXY_REGEXES = tuple(regexes)
log(f"已加载强制代理规则: 后缀 {len(suffixes)} 条、精确 {len(exact)} 条、"
f"正则 {len(regexes)} 条 (pac_domains.json)" +
(f",跳过 {invalid_regexes} 条非法正则" if invalid_regexes else ""))
except Exception as e:
log_red(f"pac_domains.json 加载失败: {e}")
_FORCE_PROXY_SUFFIXES = sorted(_CORE_FORCE_PROXY_SUFFIXES)
_FORCE_PROXY_EXACT = frozenset()
_FORCE_PROXY_REGEXES = ()
return _FORCE_PROXY_SUFFIXES
def _is_private_int(n: int) -> bool:
@@ -1386,12 +1437,16 @@ def _is_china_ip(host: str):
@functools.lru_cache(maxsize=8192)
def _is_force_proxy(host: str) -> bool:
"""pac_domains.json 命中的域名强制走隧道(如 claude.ai:国内可连但显示拒绝页)。"""
"""PAC 强制规则命中的域名走隧道(后缀 / full / regexp)。"""
suffixes = _load_force_proxy_suffixes()
if not suffixes:
exact = _FORCE_PROXY_EXACT or ()
regexes = _FORCE_PROXY_REGEXES or ()
if not suffixes and not exact and not regexes:
return False
h = host.rstrip(".").lower()
return any(h == s or h.endswith("." + s) for s in suffixes)
return (any(h == s or h.endswith("." + s) for s in suffixes) or
h in exact or
any(pattern.search(h) is not None for pattern in regexes))
def _resolve_host_ips(host: str):
"""解析域名的 IPv4/IPv6 地址集合(字面量 IP 返回自身);解析失败返回空集合。"""
@@ -3092,4 +3147,4 @@ def main():
os._exit(ret)
if __name__ == "__main__":
main()
main()