Files
OpenFlare/internal/apps/openflare/waf/graph_compile.go
T
ryan 699e95f12c perfsprint 18→0:strconv.Itoa/FormatInt/FormatUint/FormatBool 替代 fmt.Sprintf、无动词 fmt.Errorf→errors.New、纯字符串拼接。全部语义等价(已核对 diff)。修正 fixer 遗留的 import 问题(引入 goimports 统一整理)。
Result: {"status":"keep","total_issues":56,"golint_canonicalheader":8,"golint_errname":1,"golint_errorlint":12,"golint_forcetypeassert":3,"golint_gosec":0,"golint_intrange":3,"golint_modernize":5,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":3,"golint_recvcheck":7,"golint_usestdlibvars":3,"golint_wastedassign":7,"golint_total":55,"eslint_problems":1,"eslint_errors":0,"eslint_warnings":1,"tsc_errors":0,"measure_s":47}
2026-08-16 17:34:44 +08:00

157 lines
4.8 KiB
Go

// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package waf
import (
"errors"
"fmt"
"slices"
"sort"
)
// RuntimeRuleGraph is the compact graph loaded by the request runtime.
// Nodes are indexed by ID so execution does not scan the editor node list.
type RuntimeRuleGraph struct {
Entry string `json:"entry"`
Nodes map[string]RuntimeRuleNode `json:"nodes"`
}
// RuntimeRuleNode contains the typed runtime configuration and compiled exits
// for one graph node.
type RuntimeRuleNode struct {
Type RuleNodeType `json:"type"`
Config any `json:"config"`
Next map[string]string `json:"next,omitempty"`
}
// CompileRuleGraph removes editor-only fields and compiles edges into per-node
// handle lookups. Callers should validate the graph before compiling it.
func CompileRuleGraph(graph RuleGraph) (RuntimeRuleGraph, error) {
runtime := RuntimeRuleGraph{Nodes: make(map[string]RuntimeRuleNode, len(graph.Nodes))}
for _, node := range graph.Nodes {
config, err := compileRuleNodeConfig(node)
if err != nil {
return RuntimeRuleGraph{}, fmt.Errorf("节点 %s 的配置无法编译: %w", node.ID, err)
}
if node.Type == RuleNodeStart {
if runtime.Entry != "" {
return RuntimeRuleGraph{}, errors.New("规则图包含多个开始节点")
}
runtime.Entry = node.ID
}
runtime.Nodes[node.ID] = RuntimeRuleNode{Type: node.Type, Config: config}
}
if runtime.Entry == "" {
return RuntimeRuleGraph{}, errors.New("规则图缺少开始节点")
}
for _, edge := range graph.Edges {
node, ok := runtime.Nodes[edge.Source]
if !ok {
return RuntimeRuleGraph{}, fmt.Errorf("边 %s 的源节点 %s 不存在", edge.ID, edge.Source)
}
if _, ok := runtime.Nodes[edge.Target]; !ok {
return RuntimeRuleGraph{}, fmt.Errorf("边 %s 的目标节点 %s 不存在", edge.ID, edge.Target)
}
if node.Next == nil {
node.Next = make(map[string]string)
}
if _, exists := node.Next[edge.SourceHandle]; exists {
return RuntimeRuleGraph{}, fmt.Errorf("节点 %s 的 %s 出口连接了多个目标", edge.Source, edge.SourceHandle)
}
node.Next[edge.SourceHandle] = edge.Target
runtime.Nodes[edge.Source] = node
}
return runtime, nil
}
func compileRuleNodeConfig(node RuleNode) (any, error) {
switch node.Type {
case RuleNodeStart, RuleNodeAllow:
var config struct{}
return config, decodeStrictConfig(node.Config, &config)
case RuleNodeIPMatch:
var config IPMatchConfig
if err := decodeStrictConfig(node.Config, &config); err != nil {
return nil, err
}
config.IPs = sortedUniqueStrings(config.IPs)
config.CIDRs = sortedUniqueStrings(config.CIDRs)
config.IPGroupIDs = sortedUniqueUints(config.IPGroupIDs)
return config, nil
case RuleNodeGeoMatch:
var config GeoMatchConfig
if err := decodeStrictConfig(node.Config, &config); err != nil {
return nil, err
}
config.Countries = sortedUniqueStrings(config.Countries)
config.Regions = sortedUniqueStrings(config.Regions)
return config, nil
case RuleNodePoW:
var config PoWNodeConfig
return config, decodeStrictConfig(node.Config, &config)
case RuleNodeUACheck:
var config UACheckConfig
if err := decodeStrictConfig(node.Config, &config); err != nil {
return nil, err
}
config.Browsers = sortedUniqueStrings(config.Browsers)
config.OperatingSystems = sortedUniqueStrings(config.OperatingSystems)
config.CustomUAPatterns = sortedUniqueStrings(config.CustomUAPatterns)
if config.MatchMode == "" {
config.MatchMode = UACheckMatchModeOr
}
return config, nil
case RuleNodeSecurityCheck:
var config SecurityCheckConfig
return config, decodeStrictConfig(node.Config, &config)
case RuleNodeBlock:
var config BlockNodeConfig
return config, decodeStrictConfig(node.Config, &config)
default:
return nil, fmt.Errorf("未知节点类型 %s", node.Type)
}
}
// ReferencedIPGroupIDs returns the unique, sorted IP group IDs referenced by
// decodable IP match nodes. Graph validation reports malformed configurations.
func ReferencedIPGroupIDs(graph RuleGraph) []uint {
ids := make([]uint, 0)
for _, node := range graph.Nodes {
if node.Type != RuleNodeIPMatch {
continue
}
var config IPMatchConfig
if err := decodeStrictConfig(node.Config, &config); err == nil {
ids = append(ids, config.IPGroupIDs...)
}
}
return sortedUniqueUints(ids)
}
func sortedUniqueStrings(values []string) []string {
result := append([]string(nil), values...)
sort.Strings(result)
write := 0
for _, value := range result {
if write == 0 || result[write-1] != value {
result[write] = value
write++
}
}
return result[:write]
}
func sortedUniqueUints(values []uint) []uint {
result := append([]uint(nil), values...)
slices.Sort(result)
write := 0
for _, value := range result {
if write == 0 || result[write-1] != value {
result[write] = value
write++
}
}
return result[:write]
}