mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 21:50:44 +00:00
被控端 - 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%) - 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败) - 每分钟回头找局域网,自动切回快 400 倍的路径 - 画面心跳帧:静止时省流量,客户端仍知道流是活的 - 锁屏时给出可操作的提示 手表端 - 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException - 连接错误翻译成人话(证书/时间/解析/超时分别提示) - 经中转连上后用服务器报的地址刷新已配对记录 - 新增局域网连接页面、服务器延迟检测 服务器端 - 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502) - 网络调优脚本 net-tune.sh(开机自动应用) - 管理页:批量删除、网络体检、下载区 - 隧道改 HTTP/2 + 双连接 文档 - 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
216 lines
7.3 KiB
Python
216 lines
7.3 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""全项目编码审计(收窄版)。
|
||
|
||
上一版太吵:把 Java 字节码、测试用的 .bin、还有审计脚本自己
|
||
(源码里就写着乱码样本)全当成文本查了。
|
||
|
||
这一版:
|
||
* 用**内容**判断是不是二进制(不是只看扩展名)
|
||
* 跳过编译产物和测试数据目录
|
||
* 跳过审计脚本自己
|
||
|
||
只查真正要交付和要跑的东西。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
import zipfile
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parent.parent
|
||
SELF = Path(__file__).resolve()
|
||
|
||
# 这些目录是产物/数据,不是源码
|
||
SKIP_DIRS = {
|
||
"_build", "__pycache__", ".git", "dist_exe", "dist", "pyinstaller_work",
|
||
"node_modules", "out_java", "received", "gen", "javacheck", "javacheck2",
|
||
"javacheck3", "javacheck4", "javacheck5", "build_tmp", "toolchain",
|
||
}
|
||
SKIP_SUFFIX = {
|
||
".pyc", ".png", ".jpg", ".jpeg", ".gif", ".ico", ".apk", ".exe", ".dll",
|
||
".pyd", ".zip", ".idsig", ".so", ".class", ".bin", ".jar", ".keystore",
|
||
".jks", ".der", ".pem", ".7z",
|
||
}
|
||
|
||
MOJIBAKE = [
|
||
"锟斤拷", "�", "â€", "ä", "é", "è", "а", "Ñ€",
|
||
"\ufffd",
|
||
"ä¸", "å¤", "æµ", "è¯", "é€",
|
||
]
|
||
|
||
# 只查这些扩展名(其余一律不问)
|
||
TEXT_SUFFIX = {".py", ".java", ".ps1", ".sh", ".bat", ".cmd", ".txt",
|
||
".md", ".json", ".yml", ".yaml", ".xml", ".gradle", ".cfg",
|
||
".ini", ".properties", ".pro", ".service", ".timer"}
|
||
|
||
problems = []
|
||
stats = {}
|
||
scanned = 0
|
||
|
||
|
||
def report(level: str, path, message: str) -> None:
|
||
label = str(path.relative_to(ROOT)) if isinstance(path, Path) \
|
||
and str(path).startswith(str(ROOT)) else str(path)
|
||
problems.append((level, label, message))
|
||
|
||
|
||
def looks_binary(body: bytes) -> bool:
|
||
if b"\x00" in body[:4096]:
|
||
return True
|
||
# 非文本字节比例过高就当二进制
|
||
sample = body[:4096]
|
||
if not sample:
|
||
return False
|
||
weird = sum(1 for byte in sample
|
||
if byte < 8 or (14 <= byte < 32 and byte != 27))
|
||
return weird / len(sample) > 0.10
|
||
|
||
|
||
def check_bytes(path, name: str, suffix: str, raw: bytes) -> None:
|
||
"""对一份内容做编码检查。path 只用于报错定位。"""
|
||
global scanned
|
||
scanned += 1
|
||
stats[suffix] = stats.get(suffix, 0) + 1
|
||
|
||
has_bom = raw.startswith(b"\xef\xbb\xbf")
|
||
body = raw[3:] if has_bom else raw
|
||
|
||
# .bat 走特殊逻辑:cmd.exe 在中文 Windows 上默认代码页 936,
|
||
# 所以 .bat 里要写中文就必须是 **GBK**(UTF-8 的中文会变乱码)。
|
||
# 纯 ASCII 的 .bat 两种代码页下都对,最保险。
|
||
if suffix in (".bat", ".cmd"):
|
||
if has_bom:
|
||
report("警告", path, f"{name} 有 BOM(cmd 会把 BOM 当命令)")
|
||
try:
|
||
text = body.decode("gbk")
|
||
encoding_name = "GBK"
|
||
except UnicodeDecodeError:
|
||
try:
|
||
text = body.decode("utf-8")
|
||
if any("\u4e00" <= ch <= "\u9fff" for ch in text):
|
||
report("严重", path,
|
||
f"{name} 是 UTF-8 且含中文 —— cmd.exe 下会乱码,应为 GBK")
|
||
encoding_name = "UTF-8(纯ASCII)"
|
||
except UnicodeDecodeError as exc:
|
||
report("严重", path, f"{name} GBK/UTF-8 都解不开:{exc}")
|
||
return
|
||
crlf = body.count(b"\r\n")
|
||
lf_only = body.count(b"\n") - crlf
|
||
if lf_only:
|
||
report("警告", path, f"{name} 有 {lf_only} 处纯 LF(.bat 应为 CRLF)")
|
||
stats[suffix] = stats.get(suffix, 0) + 1
|
||
scanned += 1
|
||
return
|
||
|
||
try:
|
||
text = body.decode("utf-8")
|
||
except UnicodeDecodeError as exc:
|
||
report("严重", path, f"{name} 不是合法 UTF-8:{exc}")
|
||
return
|
||
|
||
if suffix == ".ps1" and not has_bom:
|
||
report("严重", path, f"{name} 缺 UTF-8 BOM(PowerShell 5.1 会按 GBK 读)")
|
||
if suffix in (".py", ".java", ".sh") and has_bom:
|
||
report("警告", path, f"{name} 不应有 BOM")
|
||
if suffix in (".bat", ".cmd") and has_bom:
|
||
report("警告", path, f"{name} 不应有 BOM")
|
||
|
||
for token in MOJIBAKE:
|
||
if token in text:
|
||
line = next((i + 1 for i, l in enumerate(text.splitlines())
|
||
if token in l), 0)
|
||
report("严重", path, f"{name} 疑似乱码 {token!r}(第 {line} 行)")
|
||
break
|
||
|
||
crlf = body.count(b"\r\n")
|
||
lf_only = body.count(b"\n") - crlf
|
||
cr_only = body.count(b"\r") - crlf
|
||
|
||
if suffix == ".sh" and (crlf or cr_only):
|
||
report("严重", path, f"{name} 有 CR(CRLF {crlf} 处)—— Linux 上跑不起来")
|
||
if suffix in (".bat", ".cmd") and lf_only:
|
||
report("警告", path, f"{name} 有 {lf_only} 处纯 LF")
|
||
if cr_only:
|
||
report("警告", path, f"{name} 有 {cr_only} 处孤立 CR")
|
||
if crlf and lf_only and suffix not in (".bat", ".cmd"):
|
||
report("提示", path, f"{name} 行尾混用(CRLF {crlf} / LF {lf_only})")
|
||
|
||
|
||
def check_zip(zip_path: Path) -> None:
|
||
if not zip_path.is_file():
|
||
return
|
||
print(f" 交付包:{zip_path.name}")
|
||
with zipfile.ZipFile(zip_path) as archive:
|
||
for name in archive.namelist():
|
||
if name.endswith("/"):
|
||
continue
|
||
suffix = Path(name).suffix.lower()
|
||
if suffix not in TEXT_SUFFIX:
|
||
continue
|
||
raw = archive.read(name)
|
||
if not raw or looks_binary(raw):
|
||
continue
|
||
check_bytes(zip_path, f"包内 {name}", suffix, raw)
|
||
|
||
|
||
def main() -> int:
|
||
print("=" * 74)
|
||
print(" 编码审计(只查要交付 / 要跑的文本文件)")
|
||
print("=" * 74)
|
||
|
||
targets = []
|
||
for path in ROOT.rglob("*"):
|
||
if not path.is_file() or path.resolve() == SELF:
|
||
continue
|
||
rel = path.relative_to(ROOT)
|
||
if set(rel.parts) & SKIP_DIRS:
|
||
continue
|
||
if path.suffix.lower() in SKIP_SUFFIX:
|
||
continue
|
||
if path.suffix.lower() not in TEXT_SUFFIX:
|
||
continue
|
||
targets.append(path)
|
||
|
||
print(f" 源码目录扫描 {len(targets)} 个文本文件…")
|
||
for path in sorted(targets):
|
||
raw = path.read_bytes()
|
||
if not raw or looks_binary(raw):
|
||
continue
|
||
check_bytes(path, path.name, path.suffix.lower(), raw)
|
||
|
||
print()
|
||
check_zip(ROOT / "dist" / "ykxmz-1.4.0.zip")
|
||
|
||
print()
|
||
print(" 按类型统计:")
|
||
for suffix, count in sorted(stats.items(), key=lambda x: -x[1]):
|
||
print(f" {suffix:<12} {count:>5} 个")
|
||
|
||
print()
|
||
print("=" * 74)
|
||
if not problems:
|
||
print(" ✓ 全部通过,没有编码问题")
|
||
return 0
|
||
|
||
by_level: dict = {}
|
||
for level, path, message in problems:
|
||
by_level.setdefault(level, []).append((path, message))
|
||
|
||
for level in ("严重", "警告", "提示"):
|
||
items = by_level.get(level, [])
|
||
if not items:
|
||
continue
|
||
print(f" 【{level}】{len(items)} 条")
|
||
for path, message in items[:20]:
|
||
print(f" {path}")
|
||
print(f" {message}")
|
||
if len(items) > 20:
|
||
print(f" …还有 {len(items) - 20} 条")
|
||
print()
|
||
print(f" 合计 {len(problems)} 条")
|
||
return 1 if by_level.get("严重") else 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|