mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2026-10-31 02:01:44 +08:00
372 lines
10 KiB
Go
372 lines
10 KiB
Go
package innerbean
|
|
|
|
import (
|
|
"SamWaf/wafenginecore/ipset"
|
|
"testing"
|
|
)
|
|
|
|
func TestRuleFunc_IPInRange(t *testing.T) {
|
|
rf := NewRuleFunc()
|
|
|
|
tests := []struct {
|
|
name string
|
|
ip string
|
|
startIP string
|
|
endIP string
|
|
expected bool
|
|
}{
|
|
{"IP在范围内-开始", "172.16.0.0", "172.16.0.0", "172.20.255.254", true},
|
|
{"IP在范围内-中间", "172.18.0.1", "172.16.0.0", "172.20.255.254", true},
|
|
{"IP在范围内-结束", "172.20.255.254", "172.16.0.0", "172.20.255.254", true},
|
|
{"IP在范围外-小于", "172.15.255.255", "172.16.0.0", "172.20.255.254", false},
|
|
{"IP在范围外-大于", "172.21.0.0", "172.16.0.0", "172.20.255.254", false},
|
|
{"192.168网段-在范围内", "192.168.0.100", "192.168.0.0", "192.168.1.254", true},
|
|
{"192.168网段-不在范围内", "192.168.2.0", "192.168.0.0", "192.168.1.254", false},
|
|
{"无效IP", "invalid", "172.16.0.0", "172.20.255.254", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := rf.IPInRange(tt.ip, tt.startIP, tt.endIP)
|
|
if result != tt.expected {
|
|
t.Errorf("IPInRange(%s, %s, %s) = %v, want %v",
|
|
tt.ip, tt.startIP, tt.endIP, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRuleFunc_IPInCIDR(t *testing.T) {
|
|
rf := NewRuleFunc()
|
|
|
|
tests := []struct {
|
|
name string
|
|
ip string
|
|
cidr string
|
|
expected bool
|
|
}{
|
|
{"IP在CIDR内", "192.168.1.100", "192.168.1.0/24", true},
|
|
{"IP不在CIDR内", "192.168.2.100", "192.168.1.0/24", false},
|
|
{"大网段-在范围内", "10.0.50.1", "10.0.0.0/8", true},
|
|
{"大网段-不在范围内", "11.0.0.1", "10.0.0.0/8", false},
|
|
{"无效IP", "invalid", "192.168.1.0/24", false},
|
|
{"无效CIDR", "192.168.1.1", "invalid", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := rf.IPInCIDR(tt.ip, tt.cidr)
|
|
if result != tt.expected {
|
|
t.Errorf("IPInCIDR(%s, %s) = %v, want %v",
|
|
tt.ip, tt.cidr, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRuleFunc_IPInRanges(t *testing.T) {
|
|
rf := NewRuleFunc()
|
|
|
|
tests := []struct {
|
|
name string
|
|
ip string
|
|
ranges []string
|
|
expected bool
|
|
}{
|
|
{
|
|
"IP在第一个范围内",
|
|
"172.18.0.1",
|
|
[]string{"172.16.0.0-172.20.255.254", "192.168.0.0-192.168.1.254"},
|
|
true,
|
|
},
|
|
{
|
|
"IP在第二个范围内",
|
|
"192.168.0.100",
|
|
[]string{"172.16.0.0-172.20.255.254", "192.168.0.0-192.168.1.254"},
|
|
true,
|
|
},
|
|
{
|
|
"IP不在任何范围内",
|
|
"10.0.0.1",
|
|
[]string{"172.16.0.0-172.20.255.254", "192.168.0.0-192.168.1.254"},
|
|
false,
|
|
},
|
|
{
|
|
"混合格式-CIDR和范围",
|
|
"192.168.1.50",
|
|
[]string{"10.0.0.0/8", "192.168.0.0-192.168.1.254"},
|
|
true,
|
|
},
|
|
{
|
|
"混合格式-在CIDR内",
|
|
"10.0.0.50",
|
|
[]string{"10.0.0.0/8", "192.168.0.0-192.168.1.254"},
|
|
true,
|
|
},
|
|
{
|
|
"单个IP匹配",
|
|
"127.0.0.1",
|
|
[]string{"127.0.0.1", "192.168.0.1"},
|
|
true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := rf.IPInRanges(tt.ip, tt.ranges...)
|
|
if result != tt.expected {
|
|
t.Errorf("IPInRanges(%s, %v) = %v, want %v",
|
|
tt.ip, tt.ranges, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRuleFunc_In(t *testing.T) {
|
|
rf := NewRuleFunc()
|
|
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
list []string
|
|
expected bool
|
|
}{
|
|
{"值在列表中", "GET", []string{"GET", "POST", "PUT"}, true},
|
|
{"值不在列表中", "DELETE", []string{"GET", "POST", "PUT"}, false},
|
|
{"空列表", "GET", []string{}, false},
|
|
{"精确匹配", "get", []string{"GET", "POST"}, false}, // 大小写敏感
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := rf.In(tt.value, tt.list...)
|
|
if result != tt.expected {
|
|
t.Errorf("In(%s, %v) = %v, want %v",
|
|
tt.value, tt.list, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRuleFunc_InIgnoreCase(t *testing.T) {
|
|
rf := NewRuleFunc()
|
|
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
list []string
|
|
expected bool
|
|
}{
|
|
{"忽略大小写匹配", "get", []string{"GET", "POST", "PUT"}, true},
|
|
{"忽略大小写匹配2", "Get", []string{"get", "post"}, true},
|
|
{"不匹配", "delete", []string{"GET", "POST"}, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := rf.InIgnoreCase(tt.value, tt.list...)
|
|
if result != tt.expected {
|
|
t.Errorf("InIgnoreCase(%s, %v) = %v, want %v",
|
|
tt.value, tt.list, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRuleFunc_ContainsAny(t *testing.T) {
|
|
rf := NewRuleFunc()
|
|
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
list []string
|
|
expected bool
|
|
}{
|
|
{"包含其中一个", "Mozilla/5.0 Googlebot", []string{"bot", "spider", "crawler"}, true},
|
|
{"包含多个", "Googlebot spider", []string{"bot", "spider"}, true},
|
|
{"不包含任何", "Mozilla/5.0 Chrome", []string{"bot", "spider"}, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := rf.ContainsAny(tt.value, tt.list...)
|
|
if result != tt.expected {
|
|
t.Errorf("ContainsAny(%s, %v) = %v, want %v",
|
|
tt.value, tt.list, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRuleFunc_IntInRange(t *testing.T) {
|
|
rf := NewRuleFunc()
|
|
|
|
tests := []struct {
|
|
name string
|
|
value int64
|
|
min int64
|
|
max int64
|
|
expected bool
|
|
}{
|
|
{"在范围内", 404, 400, 499, true},
|
|
{"最小边界", 400, 400, 499, true},
|
|
{"最大边界", 499, 400, 499, true},
|
|
{"小于范围", 399, 400, 499, false},
|
|
{"大于范围", 500, 400, 499, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := rf.IntInRange(tt.value, tt.min, tt.max)
|
|
if result != tt.expected {
|
|
t.Errorf("IntInRange(%d, %d, %d) = %v, want %v",
|
|
tt.value, tt.min, tt.max, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRuleFunc_StartsWithAny(t *testing.T) {
|
|
rf := NewRuleFunc()
|
|
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
list []string
|
|
expected bool
|
|
}{
|
|
{"以其中一个开头", "/admin/users", []string{"/admin", "/api", "/manage"}, true},
|
|
{"不以任何一个开头", "/user/profile", []string{"/admin", "/api"}, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := rf.StartsWithAny(tt.value, tt.list...)
|
|
if result != tt.expected {
|
|
t.Errorf("StartsWithAny(%s, %v) = %v, want %v",
|
|
tt.value, tt.list, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRuleFunc_EndsWithAny(t *testing.T) {
|
|
rf := NewRuleFunc()
|
|
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
list []string
|
|
expected bool
|
|
}{
|
|
{"以其中一个结尾", "/admin/login.php", []string{".php", ".asp", ".jsp"}, true},
|
|
{"不以任何一个结尾", "/admin/login.html", []string{".php", ".asp"}, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := rf.EndsWithAny(tt.value, tt.list...)
|
|
if result != tt.expected {
|
|
t.Errorf("EndsWithAny(%s, %v) = %v, want %v",
|
|
tt.value, tt.list, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRuleFunc_IPMatch(t *testing.T) {
|
|
rf := NewRuleFunc()
|
|
|
|
tests := []struct {
|
|
name string
|
|
ip string
|
|
pattern string
|
|
expected bool
|
|
}{
|
|
{"单IP命中", "1.2.3.4", "1.2.3.4", true},
|
|
{"单IP未命中", "1.2.3.5", "1.2.3.4", false},
|
|
{"CIDR命中", "10.5.6.7", "10.0.0.0/8", true},
|
|
{"CIDR未命中", "11.5.6.7", "10.0.0.0/8", false},
|
|
{"IPv4尾部通配命中", "10.10.99.1", "10.10.*.*", true},
|
|
{"IPv4尾部通配未命中", "10.11.99.1", "10.10.*.*", false},
|
|
{"IPv4中间通配命中", "10.77.1.9", "10.*.1.*", true},
|
|
{"IPv4中间通配未命中", "10.77.2.9", "10.*.1.*", false},
|
|
{"区间命中起点", "1.2.3.4", "1.2.3.4-1.2.3.99", true},
|
|
{"区间命中终点", "1.2.3.99", "1.2.3.4-1.2.3.99", true},
|
|
{"区间未命中", "1.2.3.100", "1.2.3.4-1.2.3.99", false},
|
|
{"IPv6单IP命中", "2001:db8::1", "2001:db8::1", true},
|
|
{"IPv6通配命中", "2001:db8:1:2::9", "2001:db8:*:*:*:*:*:*", true},
|
|
{"IPv6通配未命中", "2001:db9::1", "2001:db8:*:*:*:*:*:*", false},
|
|
{"v4模式不命中v6地址", "2001:db8::1", "10.10.*.*", false},
|
|
{"v6模式不命中v4地址", "10.10.1.1", "2001:db8:*:*:*:*:*:*", false},
|
|
{"非法模式恒false", "1.2.3.4", "10.1*.0.0", false},
|
|
{"非法IP恒false", "垃圾", "1.2.3.4", false},
|
|
{"空模式恒false", "1.2.3.4", "", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := rf.IPMatch(tt.ip, tt.pattern); got != tt.expected {
|
|
t.Errorf("IPMatch(%s, %s) = %v, want %v", tt.ip, tt.pattern, got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRuleFunc_IPInRanges_Wildcard(t *testing.T) {
|
|
rf := NewRuleFunc()
|
|
|
|
if !rf.IPInRanges("10.10.5.6", "192.168.0.0/16", "10.10.*.*") {
|
|
t.Error("IPInRanges 应当支持通配符写法")
|
|
}
|
|
if !rf.IPInRanges("2001:db8::5", "10.0.0.0/8", "2001:db8:*:*:*:*:*:*") {
|
|
t.Error("IPInRanges 应当支持 IPv6 通配符")
|
|
}
|
|
if rf.IPInRanges("11.10.5.6", "10.10.*.*") {
|
|
t.Error("不匹配的通配符不应命中")
|
|
}
|
|
// 等价写法现在能正确命中(原实现是纯字符串比较,::ffff:1.2.3.4 与 1.2.3.4 不相等)
|
|
if !rf.IPInRanges("::ffff:1.2.3.4", "1.2.3.4") {
|
|
t.Error("IPInRanges 单IP项应按 IP 语义比较,v4-mapped 写法应当命中")
|
|
}
|
|
}
|
|
|
|
func TestRuleFunc_IPInGroup(t *testing.T) {
|
|
rf := NewRuleFunc()
|
|
ipset.SetGroupSnapshot(nil)
|
|
|
|
// 快照为空时不 panic、恒 false
|
|
if rf.IPInGroup("1.2.3.4", "任意组") {
|
|
t.Error("快照为空时 IPInGroup 应返回 false")
|
|
}
|
|
|
|
ipset.UpsertGroupMatcher("g-office", "办公室出口", ipset.BuildMatchSet([]string{"10.10.*.*", "1.2.3.4"}))
|
|
|
|
if !rf.IPInGroup("10.10.1.1", "g-office") {
|
|
t.Error("按组短码应当命中")
|
|
}
|
|
if !rf.IPInGroup("10.10.1.1", "办公室出口") {
|
|
t.Error("按组名称应当命中")
|
|
}
|
|
if !rf.IPInGroup(" 1.2.3.4 ", "办公室出口") {
|
|
t.Error("IP 两端空白应被容忍")
|
|
}
|
|
if rf.IPInGroup("172.16.0.1", "办公室出口") {
|
|
t.Error("组外地址不应命中")
|
|
}
|
|
if rf.IPInGroup("10.10.1.1", "不存在的组") {
|
|
t.Error("未知组应返回 false")
|
|
}
|
|
|
|
// 组内容变更后立即生效(这是 IP 组的核心契约)
|
|
ipset.UpsertGroupMatcher("g-office", "办公室出口", ipset.BuildMatchSet([]string{"172.16.0.0/12"}))
|
|
if rf.IPInGroup("10.10.1.1", "办公室出口") {
|
|
t.Error("组内容已替换,旧地址不应再命中")
|
|
}
|
|
if !rf.IPInGroup("172.16.0.1", "办公室出口") {
|
|
t.Error("组内容已替换,新地址应当命中")
|
|
}
|
|
|
|
ipset.SetGroupSnapshot(nil)
|
|
}
|