190 lines
4.3 KiB
Go
190 lines
4.3 KiB
Go
package ruleio
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/prism/proxy/internal/store"
|
|
)
|
|
|
|
const ExportVersion = 1
|
|
|
|
type ExportDoc struct {
|
|
Version int `json:"version"`
|
|
Rules []ExportRule `json:"rules"`
|
|
}
|
|
|
|
type ExportRule struct {
|
|
Priority int `json:"priority"`
|
|
RuleType string `json:"rule_type"`
|
|
Payload string `json:"payload"`
|
|
Target string `json:"target"`
|
|
Enabled *bool `json:"enabled,omitempty"`
|
|
}
|
|
|
|
type ImportResult struct {
|
|
Imported int `json:"imported"`
|
|
Skipped int `json:"skipped"`
|
|
Errors []string `json:"errors,omitempty"`
|
|
}
|
|
|
|
func ruleEnabled(r ExportRule) bool {
|
|
if r.Enabled == nil {
|
|
return true
|
|
}
|
|
return *r.Enabled
|
|
}
|
|
|
|
func ToExportRules(rules []store.Rule) []ExportRule {
|
|
out := make([]ExportRule, 0, len(rules))
|
|
for _, r := range rules {
|
|
enabled := r.Enabled
|
|
out = append(out, ExportRule{
|
|
Priority: r.Priority,
|
|
RuleType: r.RuleType,
|
|
Payload: r.Payload,
|
|
Target: r.Target,
|
|
Enabled: &enabled,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func ExportJSON(rules []store.Rule) ([]byte, error) {
|
|
doc := ExportDoc{Version: ExportVersion, Rules: ToExportRules(rules)}
|
|
return json.MarshalIndent(doc, "", " ")
|
|
}
|
|
|
|
func ExportText(rules []store.Rule) string {
|
|
var b strings.Builder
|
|
for _, r := range rules {
|
|
if !r.Enabled {
|
|
continue
|
|
}
|
|
b.WriteString(formatRuleLine(r))
|
|
b.WriteByte('\n')
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func formatRuleLine(r store.Rule) string {
|
|
if r.Payload == "" {
|
|
return fmt.Sprintf("%s,%s", r.RuleType, r.Target)
|
|
}
|
|
return fmt.Sprintf("%s,%s,%s", r.RuleType, r.Payload, r.Target)
|
|
}
|
|
|
|
func ParseJSON(data []byte) ([]ExportRule, error) {
|
|
var doc ExportDoc
|
|
if err := json.Unmarshal(data, &doc); err == nil && (len(doc.Rules) > 0 || doc.Version > 0) {
|
|
return doc.Rules, nil
|
|
}
|
|
var rules []ExportRule
|
|
if err := json.Unmarshal(data, &rules); err != nil {
|
|
return nil, fmt.Errorf("invalid JSON: %w", err)
|
|
}
|
|
return rules, nil
|
|
}
|
|
|
|
func ParseText(text string) ([]ExportRule, []string) {
|
|
lines := strings.Split(text, "\n")
|
|
var out []ExportRule
|
|
var errs []string
|
|
for i, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
r, err := ParseLine(line)
|
|
if err != nil {
|
|
errs = append(errs, fmt.Sprintf("第 %d 行: %v", i+1, err))
|
|
continue
|
|
}
|
|
out = append(out, r)
|
|
}
|
|
return out, errs
|
|
}
|
|
|
|
func ParseLine(line string) (ExportRule, error) {
|
|
parts := strings.Split(line, ",")
|
|
if len(parts) < 2 {
|
|
return ExportRule{}, fmt.Errorf("格式无效 %q", line)
|
|
}
|
|
ruleType := strings.TrimSpace(strings.ToUpper(parts[0]))
|
|
if ruleType == "MATCH" {
|
|
return ExportRule{}, fmt.Errorf("跳过 MATCH 规则")
|
|
}
|
|
target := strings.TrimSpace(parts[len(parts)-1])
|
|
if target == "" {
|
|
return ExportRule{}, fmt.Errorf("缺少出站目标")
|
|
}
|
|
payload := ""
|
|
if len(parts) > 2 {
|
|
payload = strings.Join(parts[1:len(parts)-1], ",")
|
|
}
|
|
if needsPayload(ruleType) && payload == "" {
|
|
return ExportRule{}, fmt.Errorf("%s 需要匹配值", ruleType)
|
|
}
|
|
enabled := true
|
|
return ExportRule{
|
|
RuleType: ruleType,
|
|
Payload: payload,
|
|
Target: target,
|
|
Enabled: &enabled,
|
|
}, nil
|
|
}
|
|
|
|
func needsPayload(ruleType string) bool {
|
|
switch ruleType {
|
|
case "DIRECT", "REJECT", "REJECT-DROP", "MATCH":
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func ToStoreRules(rules []ExportRule) ([]store.Rule, []string) {
|
|
out := make([]store.Rule, 0, len(rules))
|
|
var errs []string
|
|
for i, r := range rules {
|
|
ruleType := strings.TrimSpace(strings.ToUpper(r.RuleType))
|
|
target := strings.TrimSpace(r.Target)
|
|
if ruleType == "" || ruleType == "MATCH" {
|
|
errs = append(errs, fmt.Sprintf("规则 %d: 类型无效", i+1))
|
|
continue
|
|
}
|
|
if target == "" {
|
|
errs = append(errs, fmt.Sprintf("规则 %d: 缺少出站目标", i+1))
|
|
continue
|
|
}
|
|
payload := strings.TrimSpace(r.Payload)
|
|
if needsPayload(ruleType) && payload == "" {
|
|
errs = append(errs, fmt.Sprintf("规则 %d: %s 需要匹配值", i+1, ruleType))
|
|
continue
|
|
}
|
|
out = append(out, store.Rule{
|
|
Priority: r.Priority,
|
|
RuleType: ruleType,
|
|
Payload: payload,
|
|
Target: target,
|
|
Source: "manual",
|
|
Enabled: ruleEnabled(r),
|
|
})
|
|
}
|
|
return out, errs
|
|
}
|
|
|
|
func AssignPriorities(rules []store.Rule, start int) {
|
|
if start <= 0 {
|
|
start = 100
|
|
}
|
|
step := 10
|
|
for i := range rules {
|
|
if rules[i].Priority <= 0 {
|
|
rules[i].Priority = start
|
|
start += step
|
|
}
|
|
}
|
|
}
|