# -*- coding: utf-8 -*- r'''断言"手表端给中转地址补协议头"这件事真的做了,而且只有一处入口。 == 为什么要专门测这个 == 用户报的故障: 填了 yk.xxmxxs.top(没写协议头)-> 点「检查服务器」 -> MalformedURLException: no protocol: yk.xxmxxs.top/v1/health 根因:`checkServer()` 直接拿 relayUrl() 的**原始字符串**拼 URL, 而补协议头的逻辑只在 RelayTransport 的构造函数里 —— 同一个地址"连接"时能用、"检查服务器"时崩。 修法是把规范化收到 relayUrl() 这一个入口。 **所以断言要盯两件事**: 1. 规范化函数存在,而且规则正确(IP 走 http、域名走 https) 2. relayUrl() 真的调了它(否则又是"改了函数没人用") 3. checkServer() 从 relayUrl() 取地址(不再自己拼原始值) ''' from __future__ import annotations import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent JAVA = ROOT / "android" / "src" / "com" / "lanremote" / "watch" MAIN = JAVA / "MainActivity.java" TRANSPORT = JAVA / "RelayTransport.java" FAILS: list = [] def check(name: str, ok: bool, why: str = "") -> None: print(" [%s] %s%s" % ("PASS" if ok else "FAIL", name, (" —— " + why) if (why and not ok) else "")) if not ok: FAILS.append(name) def main() -> int: print("=" * 58) print(" 手表端中转地址规范化") print("=" * 58) print() check("MainActivity.java 存在", MAIN.is_file()) if not MAIN.is_file(): return 1 src = MAIN.read_text(encoding="utf-8") # ---- 1. 规范化函数存在 ---- print("一、规范化函数") check("定义了 normalizeRelay()", "normalizeRelay(String raw)" in src, "没有它就又会出现 no protocol") # 从源码里把函数体抠出来检查规则 m = re.search(r"static String normalizeRelay\(String raw\)\s*\{(.*?)\n \}", src, re.S) body = m.group(1) if m else "" check("函数体抠出来了", bool(body)) check("已经有协议头就原样返回", "startsWith(\"http://\")" in body and "startsWith(\"https://\")" in body) check("空串返回空(代表只走局域网)", "url.isEmpty()" in body) check("纯 IP 走 http", 'matches("\\\\d{1,3}(\\\\.\\\\d{1,3}){3}")' in body or "matches(" in body and "d{1,3}" in body) check("localhost 走 http", "localhost" in body.lower()) check(".local 走 http", ".local" in body) check("域名走 https", '"https://"' in body) # ---- 2. relayUrl() 真的调了它 ---- print() print("二、relayUrl() 是不是唯一入口") m2 = re.search(r"private String relayUrl\(\)\s*\{(.*?)\n \}", src, re.S) ru = m2.group(1) if m2 else "" check("relayUrl() 里调了 normalizeRelay", "normalizeRelay(saved)" in ru, "没调的话 checkServer 还是会拿到没协议头的地址") check("relayUrl() 里没有自己再写一遍规范化", "startsWith(\"http" not in ru, "应该只调 normalizeRelay,别复制逻辑") # ---- 3. checkServer 用的是 relayUrl() ---- print() print("三、checkServer() 的来源") m3 = re.search(r"private void checkServer\(\)\s*\{(.*?)\n \}", src, re.S) cs = m3.group(1) if m3 else "" check("checkServer() 里有 relayUrl()", "relayUrl()" in cs) check("checkServer() 不再直接拼原始字符串", "new java.net.URL(relay + \"/v1/health\")" in cs and "String relay = relayUrl()" in cs, "应该从 relayUrl() 取(已规范化过的)") # ---- 4. 两边规则一致 ---- print() print("四、和 RelayTransport 的规则一致") tr = TRANSPORT.read_text(encoding="utf-8") check("RelayTransport 也会补协议头", '!url.startsWith("http://")' in tr) check("RelayTransport 里纯 IP 走 http", "d{1,3}" in tr) check("RelayTransport 里域名走 https", '"https://"' in tr) # ---- 5. 真实用例(模拟函数逻辑,确保规则对)---- print() print("五、规则实算(用 Python 复刻一遍,看结果对不对)") def normalize(raw: str) -> str: url = (raw or "").strip() while url.endswith("/"): url = url[:-1] if not url or url.startswith(("http://", "https://")): return url host = url if "/" in host: host = host.split("/", 1)[0] if ":" in host: host = host.split(":", 1)[0] plain = (host.lower() == "localhost" or host.lower().endswith(".local") or re.fullmatch(r"\d{1,3}(\.\d{1,3}){3}", host) is not None) return ("http://" if plain else "https://") + url cases = [ ("yk.xxmxxs.top", "https://yk.xxmxxs.top"), ("yk.xxmxxs.top/", "https://yk.xxmxxs.top"), ("https://yk.xxmxxs.top", "https://yk.xxmxxs.top"), ("http://10.20.30.40:13245", "http://10.20.30.40:13245"), ("10.20.30.40:13245", "http://10.20.30.40:13245"), ("192.168.2.156:8080", "http://192.168.2.156:8080"), ("localhost:8080", "http://localhost:8080"), ("lanremote.local:8765", "http://lanremote.local:8765"), ("", ""), ] for raw, want in cases: got = normalize(raw) check(" %-28r -> %s" % (raw, want), got == want, "实际得到 %r" % got) print() print("=" * 58) if FAILS: print(" 失败 %d 项:%s" % (len(FAILS), ", ".join(FAILS))) return 1 print(" 全部通过") return 0 if __name__ == "__main__": sys.exit(main())