mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 13:40:45 +00:00
手表端 - 功能页从 10 个杂项精简到 7 个(屏幕/鼠标/媒体/文件/电脑/语音/电源) - 删掉:游戏封锁、待办、掷骰子、抽奖、壁纸、显示器开关、定时关机、 剪贴板历史、性能/磁盘/电量、文件夹浏览、教室限制等 - 改成显式列按钮,不再把电脑端所有功能组无脑铺到表盘 - 远程鼠标控制重做:滑动区 + 左键/滚轮/右键三按钮 滚轮点按=中键、上下滑=滚动 - 新增视频/音乐播放器(手表本地播放):进度拖动、左右滑快进退10秒、 点画面播放暂停、上一/下一、播完自动下一首 电脑端 - 新增 kill_all(结束所有进程),白名单保护系统关键进程和自身 - 新增 media_list / media_pull(播放器数据源) 安全边界:只允许媒体目录,用 relative_to 判断,防同前缀兄弟目录绕过 中转站 - known_devices 从 ID 集合改成「设备ID -> 配对码」映射 - 修复:管理员改码后关机超过 6 小时,改码会被超时清理冲掉 - 老格式 state 文件平滑升级
130 lines
5.1 KiB
Python
130 lines
5.1 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""2.3.0 新命令的单元测试:media_list / media_pull / kill_all。
|
||
|
||
重点验**安全边界**,不是"能跑就行":
|
||
|
||
* `media_pull` 必须拒绝媒体目录之外的文件(否则就是个远程任意文件读取器)
|
||
* 同前缀的兄弟目录(`音乐-evil`)不能被误判成子目录
|
||
* `kill_all` 必须保护自己、父进程链、系统关键进程
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
import tempfile
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parent.parent
|
||
sys.path.insert(0, str(ROOT / "windows"))
|
||
|
||
passed = 0
|
||
failed = 0
|
||
|
||
|
||
def check(name, ok, detail=""):
|
||
global passed, failed
|
||
if ok:
|
||
passed += 1
|
||
print(" [PASS] " + name)
|
||
else:
|
||
failed += 1
|
||
print(" [FAIL] " + name + ((" —— " + str(detail)) if detail else ""))
|
||
return ok
|
||
|
||
|
||
def main() -> int:
|
||
print("=" * 62)
|
||
print(" 2.3.0 新命令测试")
|
||
print("=" * 62)
|
||
|
||
# ---------- 1. 静态检查:实现存在且关键点齐 ----��------
|
||
print()
|
||
print("一、代码存在性")
|
||
server = (ROOT / "windows" / "lanremote" / "server.py").read_text(
|
||
encoding="utf-8")
|
||
features = (ROOT / "windows" / "lanremote" / "features.py").read_text(
|
||
encoding="utf-8")
|
||
|
||
check("server.py 有 _media_list", "def _media_list(self)" in server)
|
||
check("server.py 有 _media_pull", "def _media_pull(self" in server)
|
||
check("路由 media_list", 'cmd == "media_list"' in server)
|
||
check("路由 media_pull", 'cmd == "media_pull"' in server)
|
||
check("features.py 有 _do_kill_all", "def _do_kill_all(self" in features)
|
||
check("注册了 kill_all 功能", 'add("kill_all"' in features)
|
||
|
||
# ---------- 2. 安全边界:只允许媒体目录 ----------
|
||
print()
|
||
print("二、media_pull 的安全边界")
|
||
check("用 relative_to 判断(不是字符串前缀)",
|
||
"wanted.relative_to(root)" in server,
|
||
"字符串前缀会被「音乐-evil」这种同前缀兄弟目录绕过")
|
||
check("有「不在允许的媒体目录里」的拒绝",
|
||
"不在允许的媒体目录里" in server)
|
||
check("有文件大小上限(512MB)", "512 * 1024 * 1024" in server)
|
||
check("拒绝空文件", "文件是空的" in server)
|
||
|
||
# ---------- 3. kill_all 的保护名单 ----------
|
||
print()
|
||
print("三、kill_all 的保护名单")
|
||
for name in ("csrss.exe", "winlogon.exe", "lsass.exe", "svchost.exe",
|
||
"explorer.exe", "python.exe", "ykxmz.exe"):
|
||
check("保护 %s" % name, '"%s"' % name in features,
|
||
"少了它一按就可能蓝屏/自断后路")
|
||
check("保护自己的 PID", "_protected_pids" in features)
|
||
check("追父进程链", "ParentProcessId" in features)
|
||
|
||
# ---------- 4. 真的跑一次 _media_list(用临时目录)----------
|
||
print()
|
||
print("四、_media_list 实跑")
|
||
try:
|
||
import os
|
||
os.environ.setdefault("PYTHONIOENCODING", "utf-8")
|
||
from lanremote import server as srv_mod
|
||
# 造一个假的 store(不需要 Config —— 那个类不存在,之前是我写错了)
|
||
tmp = Path(tempfile.mkdtemp(prefix="media_"))
|
||
music = tmp / "Music"
|
||
music.mkdir()
|
||
(music / "a.mp3").write_bytes(b"x" * 100)
|
||
(music / "b.mp4").write_bytes(b"y" * 200)
|
||
(music / "c.txt").write_text("not media", encoding="utf-8")
|
||
|
||
class FakeStore:
|
||
def received_dir(self):
|
||
return tmp
|
||
|
||
# 直接借 Server 的方法:构造太麻烦,用 types 绑定
|
||
import types
|
||
obj = types.SimpleNamespace()
|
||
obj.store = FakeStore()
|
||
obj.AUDIO_EXT = srv_mod.RemoteServer.AUDIO_EXT
|
||
obj.VIDEO_EXT = srv_mod.RemoteServer.VIDEO_EXT
|
||
obj.MEDIA_LIST_LIMIT = srv_mod.RemoteServer.MEDIA_LIST_LIMIT
|
||
obj.MEDIA_SUBDIRS = ("Music", "Videos")
|
||
obj._media_roots = types.MethodType(srv_mod.RemoteServer._media_roots, obj)
|
||
# Path.home() 是真实用户目录,这里把 _media_roots 换成只返回 music。
|
||
# ⚠ 必须返回**装文件的那一层**,不是它的父目录 ——
|
||
# _media_list 只扫根目录下的文件,不递归子目录。
|
||
# (第一版写成了 tmp,扫到的是 Music 这个文件夹本身,全被跳过。)
|
||
obj._media_roots = lambda: [music.resolve()]
|
||
|
||
result = srv_mod.RemoteServer._media_list(obj)
|
||
names = [f["name"] for f in result["files"]]
|
||
check("列出了 a.mp3", "a.mp3" in names)
|
||
check("列出了 b.mp4", "b.mp4" in names)
|
||
check("**排除了 c.txt**", "c.txt" not in names,
|
||
"非媒体扩展名不该出现")
|
||
kinds = {f["name"]: f["kind"] for f in result["files"]}
|
||
check("a.mp3 标成 audio", kinds.get("a.mp3") == "audio")
|
||
check("b.mp4 标成 video", kinds.get("b.mp4") == "video")
|
||
except Exception as exc: # noqa: BLE001
|
||
check("_media_list 实跑", False, "%s: %s" % (type(exc).__name__, exc))
|
||
|
||
print()
|
||
print("=" * 62)
|
||
print(" 通过 %d 项,失败 %d 项" % (passed, failed))
|
||
print("=" * 62)
|
||
return 0 if failed == 0 else 1
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|