# -*- coding: utf-8 -*- r'''收尾两个验证项。 == A. deviceId 还要拒绝路径分隔符 == 上一版只挡了"不可打印 ASCII",但 `/`、`\` 是可打印的,仍然能进来。 deviceId 会进日志、进状态文件、进管理页,不该带路径味道的字符。 真实形状是「机器名-12位hex」,用的字符集很窄。 改成白名单:字母数字 + `-` `_` `.` `:`,其余一律拒。 (`.` 和 `:` 留着是因为可能有 `host.local:8080` 这种形状的历史数据。) == B. test_verify_blank_relay.py 在测自己的副本 == 那个测试为了省事,**把 enable_relay 的实现抄了一份**放在测试里, 然后断言抄来的那份。我修了 server.py 里的真实现,测试却看不到 —— 它测的是自己那份旧代码,所以永远红。 这是"测试自己的副本"的经典反模式:实现变了测试不知道, 测试红了实现也没问题。 修法两步: 1. 把测试里的副本同步成修好之后的逻辑(它仍然模拟"没配地址"这条路) 2. **加一条源码守卫**:直接读 server.py,断言里面没有 `if self._relay is not None: return True` 这种"对象在就报成功"的写法, 并且确实用了 running 判断。 这样真实现一旦回归,测试会立刻发现 —— 副本同步与否都不影响兜底。 ''' from __future__ import annotations import sys from pathlib import Path ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") def patch(path: Path, old: str, new: str, label: str) -> bool: text = path.read_text(encoding="utf-8") if old not in text: print(" ✗ 没找到:%s" % label) return False if text.count(old) != 1: print(" ! 出现 %d 次:%s" % (text.count(old), label)) return False path.write_text(text.replace(old, new, 1), encoding="utf-8", newline="\n") print(" ✓ %s" % label) return True def main() -> int: ok_all = True # ---------------------------------------------------------- A print(" ── A. deviceId 白名单 ──") ok_all &= patch( ROOT / "relay" / "relay.py", """ # 只接受可打印 ASCII(真实 deviceId 是「机器名-12位hex」这种形状)。 # 不放行的话 `\\x00`、`\\r\\n` 这类控制字符能进来 —— # 管理页的转义做得对所以不是 XSS,但 deviceId 会写进日志, # `\\r\\n` 可以伪造日志行、把 journalctl 的输出搅乱。 if not all(32 <= ord(ch) < 127 for ch in device_id): log(f"deviceId 含不可打印字符,拒绝:{device_id!r}") return {"ok": False, "error": "deviceId 含非法字符"}""", """ # deviceId 走白名单:字母数字 + - _ . : # # 为什么要卡这么死: # * 控制字符(\\x00、\\r\\n)能进来 —— 管理页转义做得对所以 # 不是 XSS,但 deviceId 会写进日志,`\\r\\n` 能伪造日志行 # * 路径分隔符(/ \\)能进来 —— 会进状态文件和页面,不该有 # # 真实 deviceId 的形状是「机器名-12位hex」(见 _ensure_device_id), # 字符集本来就很窄,卡严不会有任何副作用。 if not all(ch.isalnum() or ch in "-_.:" for ch in device_id): log(f"deviceId 含非法字符,拒绝:{device_id!r}") return {"ok": False, "error": "deviceId 含非法字符"}""", "deviceId 白名单校验") # ---------------------------------------------------------- B1 print() print(" ── B1. 同步测试里的那份副本 ──") ok_all &= patch( ROOT / "_tests" / "test_verify_blank_relay.py", """ # 与 server.py 里 enable_relay 的实现保持一致 def enable_relay(self, url: str = "") -> bool: target = (url or "").strip() or str( store2.get("relay_url") or "") if not target.strip(): target = srv.DEFAULT_RELAY_URL if not target.strip(): self._log("没配中转地址,只走局域网" "(需要跨网络时在设置里填中转地址)") if self._relay is not None: return True self._relay = RelayLink(store2, log=self._log) return self._relay.start(target)""", """ # ⚠ 这是 server.py 里 enable_relay 的**副本**。 # 副本的问题是:实现改了它不会跟着变,于是测试红了、 # 实现其实没问题(我修完真实现时这里就红了一次)。 # 所以下面第 3.5 节加了一条**直接读源码**的守卫兜底。 def enable_relay(self, url: str = "") -> bool: target = (url or "").strip() or str( store2.get("relay_url") or "") if not target.strip(): target = srv.DEFAULT_RELAY_URL if not target.strip(): self._log("没配中转地址,只走局域网" "(需要跨网络时在设置里填中转地址)") # 真实现:看**是否真的在跑**,不是"对象建过没有" if self._relay is not None: return bool(getattr(self._relay, "running", False)) self._relay = RelayLink(store2, log=self._log) return self._relay.start(target)""", "同步测试里的副本") # ---------------------------------------------------------- B2 源码守卫 print() print(" ── B2. 加源码守卫(防止真实现回归)──") guard = ''' # ============================================================ # 3.5 源码守卫:真实现不能用「对象建过就报成功」的写法 # # 为什么需要这一节: # 上面 3 那节测的是 enable_relay 的**副本**。副本和真实现脱钩时, # 测试红/绿都不代表真实现对不对。这里直接读 server.py 断言。 # ============================================================ print() print("═══ 3.5. 源码守卫:server.py 里的真实写法 ═══") import re as _re _src_path = ROOT / "windows" / "lanremote" / "server.py" _src = _src_path.read_text(encoding="utf-8") # 找到 enable_relay 的函数体 _m = _re.search(r"def enable_relay\\b.*?(?=\\n def |\\Z)", _src, _re.S) _body = _m.group(0) if _m else "" check("能定位到 enable_relay 的实现", bool(_body), "没匹配到,可能函数改名了") if _body: # 禁止:对象存在就返回 True(没有 running 判断) check("实现里没有「对象建过就 return True」的写法", not _re.search(r"if self\\._relay is not None:\\s*\\n\\s*return True", _body), "真实现又变回谎报成功的写法了") # 必须:返回值体现真实运行状态 check("实现里检查了真实运行状态(running)", "running" in _body, "没看到 running —— 返回值可能又不反映真实状态") ''' target = ROOT / "_tests" / "test_verify_blank_relay.py" text = target.read_text(encoding="utf-8") anchor = '\n\nprint()\nprint("═══ 4. 这个返回值会影响谁 ═══")' if anchor in text: text = text.replace(anchor, guard + anchor, 1) target.write_text(text, encoding="utf-8", newline="\n") print(" ✓ 加了源码守卫") else: print(" ✗ 找不到插入点(第 4 节标题)") ok_all = False # check() 得能在模块级别用 —— 看测试里 check 是不是模块级函数 if "def check(" not in text and "check =" not in text: print(" ! 注意:check() 可能不是模块级函数,守卫可能报 NameError") # ROOT 要存在 if "ROOT = " not in text: print(" ✗ 测试里没有 ROOT,守卫会报错") ok_all = False if "import re" not in text and "_re" not in text: pass print() if not ok_all: print(" 有失败项") return 1 print(" 都改好了") return 0 if __name__ == "__main__": sys.exit(main())