package web import ( "strings" "testing" ) // mapHTMLSinks 是「可编辑字段被裸拼进 HTML」的具体写法。这些值来自地点名、交通方式名、 // 城市名、GPX 轨迹名——站长自己可编辑,但访客页也会一起渲染。 // // Leaflet 的 bindTooltip / bindPopup 把字符串参数按 **HTML** 解析,innerHTML 同理, // 所以少一层 esc() 就是一次注入。同文件里的 popup() 早就用 esc() 了, // 这四处漏掉纯属不一致(2026-09-21 审计 P1-4)。 var mapHTMLSinks = []string{ "bindTooltip(p.n", // 地点名 "bindTooltip((s.m", // 交通方式名(轨迹 tooltip) "bindTooltip(t.name", // GPX 轨迹名 "'' + c.city", // 城市名(城市气泡) "+ m.name +", // 交通方式名(图例) "+ (m.color ||", // 交通方式的颜色值,进了 style 属性 } // TestMapTooltipsEscapeEditableFields 守住地图 JS 的 HTML 出口: // 1. 已知的裸拼接写法一个都不许出现; // 2. 每一个 bindTooltip / bindPopup 的调用行要么走 esc(),要么传的是 function(自己建 DOM)。 // // 判据放在源码文本上而不是浏览器里,是因为这四处的注入后果很轻(站长可编辑的数据偏「自伤」), // 但渲染错乱是实打实的——用 CDP 起浏览器只为了证明一句 esc() 太贵。 func TestMapTooltipsEscapeEditableFields(t *testing.T) { raw, err := staticFS.ReadFile("static/js/rond-map.js") if err != nil { t.Fatalf("读取 rond-map.js 失败: %v", err) } src := string(raw) for _, bad := range mapHTMLSinks { if strings.Contains(src, bad) { t.Errorf("rond-map.js 里出现未转义的 HTML 拼接 %q:该字段可编辑,须过 esc()", bad) } } checked := 0 for i, line := range strings.Split(src, "\n") { if !strings.Contains(line, ".bindTooltip(") && !strings.Contains(line, ".bindPopup(") { continue } checked++ if !strings.Contains(line, "esc(") && !strings.Contains(line, "function") { t.Errorf("rond-map.js:%d 的 tooltip/popup 参数既没过 esc() 也不是 function,"+ "Leaflet 会按 HTML 渲染:%s", i+1, strings.TrimSpace(line)) } } if checked == 0 { t.Fatal("一个 bindTooltip/bindPopup 都没扫到:写法变了,这条守卫就失效了") } t.Logf("扫了 %d 处 tooltip/popup", checked) }