mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2026-08-30 17:20:58 +08:00
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+134
-21
@@ -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, ""
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user