# -*- coding: utf-8 -*- """验证电脑功能注册表。 功能是"电脑端声明、手表端调用"的结构,所以这里重点验证: * 声明出来的功能都能被调用(名字和实现对得上) * 读取类功能返回合理内容 * **安全边界**:文件操作不能越出用户自己的目录 * 边界是可配置的(这是给老师的实际需求留的口子) """ from __future__ import annotations import shutil import sys import tempfile import tkinter as tk 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(f" [PASS] {name}") else: FAILED += 1 print(f" [FAIL] {name} {detail}") def main() -> int: from lanremote.actions import Actions from lanremote.config import Store from lanremote.features import Features tmp = Path(tempfile.mkdtemp()) store = Store(tmp) class FakeUI: root = tk.Tk() root.withdraw() def ui_call(self, fn, timeout=5.0): return fn() def screen_size(self): return (1920, 1080) def notify(self, *a, **k): pass def log(self, *a, **k): pass def show_message(self, *a, **k): pass actions = Actions(FakeUI(), store) features = Features(actions) print("一、声明与实现是否对得上") defs = features.defs() check("功能数量够多", len(defs) >= 30, f"{len(defs)} 个") print(f" 共 {len(defs)} 个功能") groups = {} for item in defs: groups.setdefault(item.get("group", "?"), 0) groups[item["group"]] += 1 for name, count in sorted(groups.items()): print(f" {name}: {count} 个") missing = [] for item in defs: if not hasattr(features, f"_do_{item['name']}"): missing.append(item["name"]) check("每个声明的功能都有实现", not missing, str(missing)) bad_kind = [i["name"] for i in defs if i.get("kind") not in ("toggle", "action", "value", "input", "file")] check("kind 取值合法", not bad_kind, str(bad_kind)) need_input = [i for i in defs if i.get("kind") in ("input", "file")] check("需要输入的功能都标了参数名", all(i.get("key") for i in need_input), str([i["name"] for i in need_input if not i.get("key")])) print() print("二、读取类功能") reply_map = { "uptime": {}, "perf": {}, "disk": {}, "battery": {}, "network_speed": {}, "recent_files": {}, "temperature": {}, } for name in reply_map: try: text = features.run(name, {}) ok = isinstance(text, str) and len(text) > 0 except Exception as exc: # noqa: BLE001 text, ok = str(exc), False print(f" {name}: {str(text)[:60]}") check(f"{name} 能返回内容", ok) print() print("三、笔记 / 待办 / 收藏夹") features.run("note_add", {"text": "第一节课要讲第三章"}) text = features.run("note_list", {}) check("笔记能记能读", "第三章" in text, text[:80]) features.run("todo_add", {"text": "批改作业"}) text = features.run("todo_list", {}) check("待办能加能读", "批改作业" in text, text[:80]) actions.clip_set("这段是要收藏的内容") text = features.run("clip_fav_add", {}) check("剪贴板收藏能加", "已收藏" in text, text) text = features.run("clip_fav_list", {}) check("收藏夹能读", "这段是要收藏的内容" in text, text[:80]) print() print("四、安全边界(重点)") escapes = [ r"C:\Windows\System32", r"C:\Windows\System32\drivers\etc\hosts", r"..\..\Windows", "/etc/passwd", "C:\\", ] for bad in escapes: try: resolved = features.resolve_in_roots(bad) check(f"拒绝越权路径 {bad}", False, f"竟然解析成 {resolved}") except ValueError: check(f"拒绝越权路径 {bad}", True) try: features.resolve_in_roots("Downloads") check("允许自己的目录(Downloads)", True) except ValueError as exc: check("允许自己的目录(Downloads)", False, str(exc)) # 删除功能只能删允许范围内的 try: features.run("delete_file", {"path": r"C:\Windows\notepad.exe"}) check("删除功能也不能越界", False, "竟然允许了") except ValueError: check("删除功能也不能越界", True) print() print("五、边界可配置") # 额外目录要建在数据目录**外面** —— 数据目录整个都是敏感的 # (里面有配对码和设备令牌),建在里面会被正确地拒绝。 import tempfile as _tf extra = Path(_tf.mkdtemp(prefix="lr_extra_")) / "课件" extra.mkdir(parents=True, exist_ok=True) store.set("file_roots", str(extra)) roots = features.allowed_roots() check("额外目录能被加进白名单", any(str(extra) == str(r) for r in roots), str(roots[-3:])) try: resolved = features.resolve_in_roots(str(extra / "第一课.pptx")) check("新加的目录内可以访问", str(extra) in str(resolved)) except ValueError as exc: check("新加的目录内可以访问", False, str(exc)) store.set("extra_apps", "投影仪=C:\\Program Files\\App\\proj.exe") apps = features.extra_apps() check("自定义程序能配置", "投影仪" in apps, str(apps)) store.set("extra_commands", "重启投影=cmd /c restart-proj") cmds = features.extra_commands() check("自定义命令能配置", "重启投影" in cmds, str(cmds)) print() print("六、不存在/非法功能名") for bad in ("not_a_feature", "", "delete_everything"): try: features.run(bad, {}) check(f"拒绝未登记的功能 {bad!r}", False, "竟然执行了") except ValueError: check(f"拒绝未登记的功能 {bad!r}", True) shutil.rmtree(tmp, ignore_errors=True) print() print("=" * 62) print(f"通过 {PASSED} 项,失败 {FAILED} 项") return 1 if FAILED else 0 if __name__ == "__main__": sys.exit(main())