diff --git a/api/waf_system_config.go b/api/waf_system_config.go index 1734d7c..7b121ca 100644 --- a/api/waf_system_config.go +++ b/api/waf_system_config.go @@ -94,6 +94,30 @@ func (w *WafSystemConfigApi) ModifyApi(c *gin.Context) { response.FailWithMessage("解析失败", c) } } + +// ModifyByItemApi 通过 item 修改系统配置的 value +func (w *WafSystemConfigApi) ModifyByItemApi(c *gin.Context) { + var req request.WafSystemConfigEditByItemReq + err := c.ShouldBindJSON(&req) + if err != nil { + response.FailWithMessage("解析失败", c) + return + } + + if req.Item == "" { + response.FailWithMessage("item 不能为空", c) + return + } + + err = wafSystemConfigService.ModifyByItemApi(req) + if err != nil { + response.FailWithMessage("编辑发生错误: "+err.Error(), c) + } else { + waftask.TaskLoadSetting(true) + response.OkWithMessage("编辑成功", c) + } +} + func (w *WafSystemConfigApi) GetDetailByItemApi(c *gin.Context) { var req request.WafSystemConfigDetailByItemReq err := c.ShouldBind(&req) diff --git a/model/request/waf_system_config_req.go b/model/request/waf_system_config_req.go index 29bc13a..be9f8ee 100644 --- a/model/request/waf_system_config_req.go +++ b/model/request/waf_system_config_req.go @@ -33,3 +33,7 @@ type WafSystemConfigSearchReq struct { type WafSystemConfigDetailByItemReq struct { Item string `json:"item" form:"item"` //item } +type WafSystemConfigEditByItemReq struct { + Item string `json:"item" form:"item"` //item + Value string `json:"value" form:"value"` //value +} diff --git a/router/waf_system_config.go b/router/waf_system_config.go index a4bf4bb..3cee765 100644 --- a/router/waf_system_config.go +++ b/router/waf_system_config.go @@ -17,4 +17,5 @@ func (receiver *SystemConfigRouter) InitSystemConfigRouter(group *gin.RouterGrou router.POST("/api/v1/systemconfig/add", api.AddApi) router.GET("/api/v1/systemconfig/del", api.DelApi) router.POST("/api/v1/systemconfig/edit", api.ModifyApi) + router.POST("/api/v1/systemconfig/editByItem", api.ModifyByItemApi) } diff --git a/service/waf_service/waf_system_config.go b/service/waf_service/waf_system_config.go index 2c3dc76..8ac3893 100644 --- a/service/waf_service/waf_system_config.go +++ b/service/waf_service/waf_system_config.go @@ -58,6 +58,25 @@ func (receiver *WafSystemConfigService) ModifyApi(req request.WafSystemConfigEdi return err } + +// ModifyByItemApi 通过 item 修改系统配置的 value +func (receiver *WafSystemConfigService) ModifyByItemApi(req request.WafSystemConfigEditByItemReq) error { + var sysConfig model.SystemConfig + err := global.GWAF_LOCAL_DB.Where("item = ?", req.Item).First(&sysConfig).Error + if err != nil { + return errors.New("配置项不存在") + } + + editMap := map[string]interface{}{ + "Value": req.Value, + "UPDATE_TIME": customtype.JsonTime(time.Now()), + } + + err = global.GWAF_LOCAL_DB.Model(model.SystemConfig{}).Where("item = ?", req.Item).Updates(editMap).Error + + return err +} + func (receiver *WafSystemConfigService) GetDetailApi(req request.WafSystemConfigDetailReq) model.SystemConfig { var bean model.SystemConfig global.GWAF_LOCAL_DB.Where("id=?", req.Id).Find(&bean) diff --git a/wafenginecore/wafengine.go b/wafenginecore/wafengine.go index 81e7545..c7dd81a 100644 --- a/wafenginecore/wafengine.go +++ b/wafenginecore/wafengine.go @@ -73,9 +73,10 @@ type WafEngine struct { } func (waf *WafEngine) Error() string { - fs := "HTTP: %d, HostCode: %d, Message: %s" - zlog.Error(fmt.Sprintf(fs)) - return fmt.Sprintf(fs) + // 返回错误信息的固定格式 + const errFormat = "WafEngine error occurred" + zlog.Error(errFormat) + return errFormat } // inferAttackType 根据检测规则标题推断攻击类型 @@ -440,26 +441,23 @@ func (waf *WafEngine) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if host == hostTarget.Host.Host+":80" && !strings.HasPrefix(weblogbean.URL, global.GSSL_HTTP_CHANGLE_PATH) && hostTarget.Host.AutoJumpHTTPS == 1 && hostTarget.Host.Ssl == 1 { - domainJump := "" - if strings.Contains(host, ":") { - partsJump := strings.Split(host, ":") - if len(partsJump) == 2 { - domainJump = partsJump[0] + if r.TLS == nil { + // 检查是否需要自动跳转到HTTPS + shouldJump, domainJump := shouldAutoJumpHTTPS(host, hostTarget.Host.Host, weblogbean.URL, hostTarget.Host.AutoJumpHTTPS, hostTarget.Host.Ssl) + if shouldJump { + // 重定向到 HTTPS 版本的 URL + targetHttpsUrl := fmt.Sprintf("%s%s%s", "https://", domainJump, r.URL.Path) + // 只有在非标准端口时才显示端口号(443是标准端口) + if hostTarget.Host.Port != 443 { + targetHttpsUrl = fmt.Sprintf("%s%s:%d%s", "https://", domainJump, hostTarget.Host.Port, r.URL.Path) } + if r.URL.RawQuery != "" { + targetHttpsUrl += "?" + r.URL.RawQuery + } + zlog.Info(innerLogName, "jump https", targetHttpsUrl) + http.Redirect(w, r, targetHttpsUrl, int(global.GCONFIG_RECORD_REDIRECT_HTTPS_CODE)) + return } - // 重定向到 HTTPS 版本的 URL - targetHttpsUrl := fmt.Sprintf("%s%s%s", "https://", domainJump, r.URL.Path) - // 只有在非标准端口时才显示端口号(443是标准端口) - if hostTarget.Host.Port != 443 { - targetHttpsUrl = fmt.Sprintf("%s%s:%d%s", "https://", domainJump, hostTarget.Host.Port, r.URL.Path) - } - if r.URL.RawQuery != "" { - targetHttpsUrl += "?" + r.URL.RawQuery - } - zlog.Info(innerLogName, "jump https", targetHttpsUrl) - http.Redirect(w, r, targetHttpsUrl, int(global.GCONFIG_RECORD_REDIRECT_HTTPS_CODE)) - return } r.Header.Add("waf_req_uuid", weblogbean.REQ_UUID) @@ -1817,3 +1815,118 @@ func (waf *WafEngine) checkWithPlugins(r *http.Request, weblogbean *innerbean.We // 插件检查通过,不拦截 return detection.Result{IsBlock: false} } + +// shouldAutoJumpHTTPS 判断是否应该自动跳转到HTTPS +// 参数: +// - requestHost: 请求的host(如: aaa.samwaf.com:80 或 bbb.aaa.samwaf.com:8080) +// - configHost: 配置的host(如: *.samwaf.com 或 example.com) +// - requestURL: 请求的URL路径 +// - autoJumpHTTPS: 是否开启自动跳转HTTPS +// - ssl: 是否启用了SSL +// +// 返回: +// - bool: 是否应该跳转 +// - string: 跳转的域名部分(不含端口) +// +// 支持场景: +// - 精确匹配: example.com:80 匹配 example.com +// - 二级泛域名: aaa.samwaf.com:80 匹配 *.samwaf.com +// - 多级泛域名: bbb.aaa.samwaf.com:8080 匹配 *.samwaf.com (利用MaskSubdomain) +func shouldAutoJumpHTTPS(requestHost, configHost, requestURL string, autoJumpHTTPS, ssl int) (bool, string) { + // 检查基本条件:必须开启自动跳转和SSL + if autoJumpHTTPS != 1 || ssl != 1 { + return false, "" + } + + // 排除SSL证书验证路径 + if strings.HasPrefix(requestURL, global.GSSL_HTTP_CHANGLE_PATH) { + return false, "" + } + + // 从请求host中提取域名和端口 + requestDomain := "" + requestPort := "" + if strings.Contains(requestHost, ":") { + parts := strings.Split(requestHost, ":") + if len(parts) == 2 { + requestDomain = parts[0] + requestPort = parts[1] + } + } else { + requestDomain = requestHost + } + + // 判断是否是HTTP端口(80或其他非443端口) + // 如果没有端口号或者端口是443,不需要跳转 + if requestPort == "" || requestPort == "443" { + return false, "" + } + + // 检查域名是否匹配 + matched := false + + // 情况1: 精确匹配(如: example.com 匹配 example.com) + if requestDomain == configHost { + matched = true + } + + // 情况2: 配置host带端口的精确匹配(如: example.com:80 匹配 example.com:80) + if !matched && strings.Contains(configHost, ":") { + if requestHost == configHost { + matched = true + } + } + + // 情况3: 泛域名匹配(支持多级域名) + // 例如: aaa.samwaf.com:80 匹配 *.samwaf.com + // bbb.aaa.samwaf.com:80 匹配 *.samwaf.com + // ccc.bbb.aaa.samwaf.com:8080 匹配 *.samwaf.com + if !matched && strings.HasPrefix(configHost, "*.") { + // 方法1: 直接匹配(二级域名) + // 提取主域名部分(去掉*.) + baseDomain := configHost[2:] // 去掉 "*." + // 检查请求域名是否以主域名结尾 + if strings.HasSuffix(requestDomain, baseDomain) { + // 确保是完整的子域名匹配,而不是部分匹配 + // 例如: aaa.samwaf.com 匹配 *.samwaf.com + // 但 aaasamwaf.com 不匹配 *.samwaf.com + if requestDomain != baseDomain && strings.HasSuffix(requestDomain, "."+baseDomain) { + matched = true + } + } + + // 方法2: 使用MaskSubdomain处理多级域名 + // 例如: bbb.aaa.samwaf.com:80 -> *.aaa.samwaf.com:80 + // 然后递归检查 *.aaa.samwaf.com 是否匹配 *.samwaf.com + if !matched { + maskedHost := domaintool.MaskSubdomain(requestHost) + // maskedHost 示例: *.aaa.samwaf.com:80 + // 如果 maskedHost 和 requestHost 不同,说明有多级域名 + if maskedHost != requestHost { + // 提取 maskedHost 的域名部分(去掉端口) + maskedDomain := maskedHost + if strings.Contains(maskedHost, ":") { + maskedDomain = strings.Split(maskedHost, ":")[0] + } + + // 递归检查: *.aaa.samwaf.com 是否匹配 *.samwaf.com + // 即检查 aaa.samwaf.com 是否匹配 *.samwaf.com 的baseDomain + if strings.HasPrefix(maskedDomain, "*.") { + checkDomain := maskedDomain[2:] // 去掉 "*.",得到 aaa.samwaf.com + // 检查 aaa.samwaf.com 是否匹配 *.samwaf.com + if strings.HasSuffix(checkDomain, baseDomain) { + if checkDomain != baseDomain && strings.HasSuffix(checkDomain, "."+baseDomain) { + matched = true + } + } + } + } + } + } + + if matched { + return true, requestDomain + } + + return false, "" +} diff --git a/wafenginecore/wafengine_test.go b/wafenginecore/wafengine_test.go index e083428..4e6b02e 100644 --- a/wafenginecore/wafengine_test.go +++ b/wafenginecore/wafengine_test.go @@ -274,7 +274,7 @@ func TestGetOrgContent(t *testing.T) { } // 调用测试函数 - result, _, err := waf.getOrgContent(resp, false) + result, _, err := waf.getOrgContent(resp, false, "") // 验证结果 if tc.expectedErr { @@ -337,7 +337,7 @@ func TestGetOrgContentWithChunkedEncoding(t *testing.T) { resp.Header.Set("Content-Type", "text/html; charset=utf-8") // 调用测试函数 - result, _, err := waf.getOrgContent(resp, false) + result, _, err := waf.getOrgContent(resp, false, "") // 验证结果 if err != nil { @@ -365,7 +365,7 @@ func TestGetOrgContentWithEmptyBody(t *testing.T) { resp.Header.Set("Content-Type", "text/html; charset=utf-8") // 调用测试函数 - result, _, err := waf.getOrgContent(resp, false) + result, _, err := waf.getOrgContent(resp, false, "") // 验证结果 if err != nil { @@ -399,7 +399,7 @@ func TestGetOrgContentWithErrors(t *testing.T) { resp.Header.Set("Content-Encoding", "gzip") // 调用测试函数 - _, _, err := waf.getOrgContent(resp, false) + _, _, err := waf.getOrgContent(resp, false, "") // 验证结果 if err == nil { @@ -422,7 +422,7 @@ func TestGetOrgContent_MetaAndDoctypeCharset(t *testing.T) { Body: io.NopCloser(strings.NewReader(htmlMetaUtf8)), } resp1.Header.Set("Content-Type", "text/html") - content1, charset1, err1 := waf.getOrgContent(resp1, false) + content1, charset1, err1 := waf.getOrgContent(resp1, false, "") if err1 != nil || charset1 != "utf-8" || !strings.Contains(string(content1), "utf8内容") { t.Errorf("meta标签utf-8检测失败: err=%v, charset=%s, content=%s", err1, charset1, string(content1)) } @@ -435,7 +435,7 @@ func TestGetOrgContent_MetaAndDoctypeCharset(t *testing.T) { Body: io.NopCloser(bytes.NewReader(gbkBody)), } resp2.Header.Set("Content-Type", "text/html") - content2, charset2, err2 := waf.getOrgContent(resp2, false) + content2, charset2, err2 := waf.getOrgContent(resp2, false, "") if err2 != nil || charset2 != "gbk" || !strings.Contains(string(content2), "这是GBK编码的内容") { t.Errorf("meta标签gbk检测失败: err=%v, charset=%s, content=%s", err2, charset2, string(content2)) } @@ -448,7 +448,7 @@ func TestGetOrgContent_MetaAndDoctypeCharset(t *testing.T) { Body: io.NopCloser(strings.NewReader(htmlDoctype)), } resp3.Header.Set("Content-Type", "text/html") - content3, charset3, err3 := waf.getOrgContent(resp3, false) + content3, charset3, err3 := waf.getOrgContent(resp3, false, "") if err3 != nil || charset3 != "utf-8" || !strings.Contains(string(content3), "doctype内容") { t.Errorf("DOCTYPE声明检测失败: err=%v, charset=%s, content=%s", err3, charset3, string(content3)) } @@ -461,8 +461,459 @@ func TestGetOrgContent_MetaAndDoctypeCharset(t *testing.T) { Body: io.NopCloser(strings.NewReader(htmlMetaUnknown)), } resp4.Header.Set("Content-Type", "text/html") - _, _, err4 := waf.getOrgContent(resp4, false) + _, _, err4 := waf.getOrgContent(resp4, false, "") if err4 == nil || !strings.Contains(err4.Error(), "编码检测不确定") { t.Errorf("未知meta编码检测失败,期望返回错误,实际: %v", err4) } } + +// TestShouldAutoJumpHTTPS 测试HTTPS自动跳转判断逻辑 +func TestShouldAutoJumpHTTPS(t *testing.T) { + // 初始化全局变量(如果有的话) + global.GSSL_HTTP_CHANGLE_PATH = "/.well-known/acme-challenge/" + + tests := []struct { + name string + requestHost string + configHost string + requestURL string + autoJumpHTTPS int + ssl int + expectedJump bool + expectedDomain string + description string + }{ + // ========== 基本条件测试 ========== + { + name: "未开启自动跳转", + requestHost: "example.com:80", + configHost: "example.com", + requestURL: "/test", + autoJumpHTTPS: 0, // 未开启 + ssl: 1, + expectedJump: false, + expectedDomain: "", + description: "autoJumpHTTPS=0,不应该跳转", + }, + { + name: "未启用SSL", + requestHost: "example.com:80", + configHost: "example.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 0, // 未启用SSL + expectedJump: false, + expectedDomain: "", + description: "ssl=0,不应该跳转", + }, + { + name: "SSL证书验证路径不跳转", + requestHost: "example.com:80", + configHost: "example.com", + requestURL: "/.well-known/acme-challenge/xxxxx", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: false, + expectedDomain: "", + description: "SSL证书验证路径应排除", + }, + + // ========== 端口测试 ========== + { + name: "标准80端口应跳转", + requestHost: "example.com:80", + configHost: "example.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "example.com", + description: "标准80端口应触发跳转", + }, + { + name: "非标准8080端口应跳转", + requestHost: "example.com:8080", + configHost: "example.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "example.com", + description: "8080端口应触发跳转", + }, + { + name: "非标准8888端口应跳转", + requestHost: "example.com:8888", + configHost: "example.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "example.com", + description: "8888端口应触发跳转", + }, + { + name: "443端口不应跳转", + requestHost: "example.com:443", + configHost: "example.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: false, + expectedDomain: "", + description: "443端口已是HTTPS,不应跳转", + }, + { + name: "无端口不应跳转", + requestHost: "example.com", + configHost: "example.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: false, + expectedDomain: "", + description: "无端口号不应跳转", + }, + + // ========== 精确匹配测试 ========== + { + name: "精确匹配-域名完全一致", + requestHost: "example.com:80", + configHost: "example.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "example.com", + description: "请求域名与配置域名完全一致应跳转", + }, + { + name: "精确匹配-域名不一致", + requestHost: "test.com:80", + configHost: "example.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: false, + expectedDomain: "", + description: "请求域名与配置域名不一致不应跳转", + }, + { + name: "带端口的精确匹配", + requestHost: "example.com:8080", + configHost: "example.com:8080", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "example.com", + description: "配置host带端口且完全匹配应跳转", + }, + + // ========== 二级泛域名测试 ========== + { + name: "二级泛域名-正常匹配", + requestHost: "aaa.samwaf.com:80", + configHost: "*.samwaf.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "aaa.samwaf.com", + description: "二级域名应匹配泛域名配置", + }, + { + name: "二级泛域名-非标准端口", + requestHost: "bbb.samwaf.com:8080", + configHost: "*.samwaf.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "bbb.samwaf.com", + description: "二级域名+非标准端口应匹配泛域名配置", + }, + { + name: "二级泛域名-根域名不应匹配", + requestHost: "samwaf.com:80", + configHost: "*.samwaf.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: false, + expectedDomain: "", + description: "根域名不应匹配*.samwaf.com", + }, + { + name: "二级泛域名-部分匹配不应成功", + requestHost: "aaasamwaf.com:80", + configHost: "*.samwaf.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: false, + expectedDomain: "", + description: "aaasamwaf.com不应匹配*.samwaf.com", + }, + + // ========== 三级泛域名测试 ========== + { + name: "三级泛域名-正常匹配", + requestHost: "bbb.aaa.samwaf.com:80", + configHost: "*.samwaf.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "bbb.aaa.samwaf.com", + description: "三级域名应匹配泛域名配置(通过MaskSubdomain)", + }, + { + name: "三级泛域名-非标准端口", + requestHost: "ccc.bbb.samwaf.com:8080", + configHost: "*.samwaf.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "ccc.bbb.samwaf.com", + description: "三级域名+非标准端口应匹配泛域名配置", + }, + + // ========== 四级泛域名测试 ========== + { + name: "四级泛域名-正常匹配", + requestHost: "ddd.ccc.bbb.samwaf.com:80", + configHost: "*.samwaf.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "ddd.ccc.bbb.samwaf.com", + description: "四级域名应匹配泛域名配置", + }, + { + name: "四级泛域名-非标准端口8888", + requestHost: "eee.ddd.ccc.samwaf.com:8888", + configHost: "*.samwaf.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "eee.ddd.ccc.samwaf.com", + description: "四级域名+8888端口应匹配泛域名配置", + }, + + // ========== 边界情况测试 ========== + { + name: "空路径", + requestHost: "example.com:80", + configHost: "example.com", + requestURL: "", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "example.com", + description: "空路径应正常跳转", + }, + { + name: "根路径", + requestHost: "example.com:80", + configHost: "example.com", + requestURL: "/", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "example.com", + description: "根路径应正常跳转", + }, + { + name: "带查询参数的路径", + requestHost: "example.com:80", + configHost: "example.com", + requestURL: "/test?param=value", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "example.com", + description: "带查询参数应正常跳转", + }, + + // ========== 不同域名后缀测试 ========== + { + name: "不同域名后缀-com", + requestHost: "test.example.com:80", + configHost: "*.example.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "test.example.com", + description: ".com域名应正常匹配", + }, + { + name: "不同域名后缀-cn", + requestHost: "test.example.cn:80", + configHost: "*.example.cn", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "test.example.cn", + description: ".cn域名应正常匹配", + }, + { + name: "不同域名后缀-io", + requestHost: "api.service.io:8080", + configHost: "*.service.io", + requestURL: "/api/v1", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "api.service.io", + description: ".io域名+非标准端口应正常匹配", + }, + + // ========== 特殊字符测试 ========== + { + name: "域名含中划线", + requestHost: "test-api.example.com:80", + configHost: "*.example.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "test-api.example.com", + description: "域名包含中划线应正常匹配", + }, + { + name: "域名含数字", + requestHost: "api123.example.com:80", + configHost: "*.example.com", + requestURL: "/test", + autoJumpHTTPS: 1, + ssl: 1, + expectedJump: true, + expectedDomain: "api123.example.com", + description: "域名包含数字应正常匹配", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotJump, gotDomain := shouldAutoJumpHTTPS( + tt.requestHost, + tt.configHost, + tt.requestURL, + tt.autoJumpHTTPS, + tt.ssl, + ) + + if gotJump != tt.expectedJump { + t.Errorf("%s\n请求Host: %s, 配置Host: %s\n期望跳转: %v, 实际跳转: %v", + tt.description, tt.requestHost, tt.configHost, tt.expectedJump, gotJump) + } + + if gotJump && gotDomain != tt.expectedDomain { + t.Errorf("%s\n请求Host: %s, 配置Host: %s\n期望域名: %s, 实际域名: %s", + tt.description, tt.requestHost, tt.configHost, tt.expectedDomain, gotDomain) + } + + // 成功的测试用例也输出日志 + if gotJump == tt.expectedJump { + t.Logf("✅ %s - 通过", tt.name) + } + }) + } +} + +// TestShouldAutoJumpHTTPS_Comprehensive 综合场景测试 +func TestShouldAutoJumpHTTPS_Comprehensive(t *testing.T) { + global.GSSL_HTTP_CHANGLE_PATH = "/.well-known/acme-challenge/" + + // 场景1: 用户反馈的实际问题场景 + t.Run("用户反馈场景1-非标准80端口", func(t *testing.T) { + // 场景:用户使用8080端口,之前无法跳转 + jump, domain := shouldAutoJumpHTTPS("example.com:8080", "example.com", "/test", 1, 1) + if !jump || domain != "example.com" { + t.Errorf("非标准80端口应该能跳转,实际: jump=%v, domain=%s", jump, domain) + } else { + t.Logf("✅ 非标准80端口测试通过") + } + }) + + t.Run("用户反馈场景2-泛域名匹配", func(t *testing.T) { + // 场景:host变量: aaa.samwaf.com:80, hostTarget.Host.Host变量: *.samwaf.com + jump, domain := shouldAutoJumpHTTPS("aaa.samwaf.com:80", "*.samwaf.com", "/test", 1, 1) + if !jump || domain != "aaa.samwaf.com" { + t.Errorf("泛域名应该能匹配,实际: jump=%v, domain=%s", jump, domain) + } else { + t.Logf("✅ 泛域名匹配测试通过") + } + }) + + t.Run("用户反馈场景3-三级泛域名", func(t *testing.T) { + // 场景:bbb.aaa.samwaf.com:80 匹配 *.samwaf.com + jump, domain := shouldAutoJumpHTTPS("bbb.aaa.samwaf.com:80", "*.samwaf.com", "/test", 1, 1) + if !jump || domain != "bbb.aaa.samwaf.com" { + t.Errorf("三级泛域名应该能匹配,实际: jump=%v, domain=%s", jump, domain) + } else { + t.Logf("✅ 三级泛域名匹配测试通过") + } + }) + + // 场景2: SSL证书自动续期路径 + t.Run("SSL证书续期路径不应跳转", func(t *testing.T) { + paths := []string{ + "/.well-known/acme-challenge/test123", + "/.well-known/acme-challenge/", + } + for _, path := range paths { + jump, _ := shouldAutoJumpHTTPS("example.com:80", "example.com", path, 1, 1) + if jump { + t.Errorf("SSL证书路径 %s 不应跳转", path) + } else { + t.Logf("✅ SSL证书路径 %s 正确排除", path) + } + } + }) + + // 场景3: 多个非标准端口 + t.Run("各种非标准HTTP端口", func(t *testing.T) { + ports := []string{"80", "8080", "8888", "9090", "3000", "5000"} + for _, port := range ports { + requestHost := "example.com:" + port + jump, domain := shouldAutoJumpHTTPS(requestHost, "example.com", "/test", 1, 1) + if !jump || domain != "example.com" { + t.Errorf("端口 %s 应该能跳转,实际: jump=%v, domain=%s", port, jump, domain) + } else { + t.Logf("✅ 端口 %s 测试通过", port) + } + } + }) + + // 场景4: 多级域名递归测试 + t.Run("多级域名递归匹配", func(t *testing.T) { + testCases := []struct { + requestHost string + configHost string + }{ + {"a.samwaf.com:80", "*.samwaf.com"}, + {"b.a.samwaf.com:80", "*.samwaf.com"}, + {"c.b.a.samwaf.com:80", "*.samwaf.com"}, + {"d.c.b.a.samwaf.com:80", "*.samwaf.com"}, + {"e.d.c.b.a.samwaf.com:80", "*.samwaf.com"}, + } + + for _, tc := range testCases { + jump, domain := shouldAutoJumpHTTPS(tc.requestHost, tc.configHost, "/test", 1, 1) + expectedDomain := strings.Split(tc.requestHost, ":")[0] + if !jump || domain != expectedDomain { + t.Errorf("多级域名 %s 匹配 %s 失败,实际: jump=%v, domain=%s", + tc.requestHost, tc.configHost, jump, domain) + } else { + t.Logf("✅ 多级域名 %s 匹配成功", tc.requestHost) + } + } + }) +}