Files
Prism/internal/ruleio/ruleio.go
T
renjue fe8ea784d0
CI / docker (push) Successful in 2m54s
feat: Prism HTTP/SOCKS5 代理网关及管理台
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 15:58:28 +08:00

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
}
}
}