diff --git a/config/config.go b/config/config.go index c7107d50..673883c4 100644 --- a/config/config.go +++ b/config/config.go @@ -1035,46 +1035,20 @@ func parseRules(rulesConfig []string, proxies map[string]C.Proxy, ruleProviders // parse rules for idx, line := range rulesConfig { - rule := trimArr(strings.Split(line, ",")) - var ( - payload string - target string - params []string - ruleName = strings.ToUpper(rule[0]) - ) - - l := len(rule) - - if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" || ruleName == "SUB-RULE" || ruleName == "DOMAIN-REGEX" || ruleName == "PROCESS-NAME-REGEX" || ruleName == "PROCESS-PATH-REGEX" { - target = rule[l-1] - payload = strings.Join(rule[1:l-1], ",") - } else { - if l < 2 { - return nil, fmt.Errorf("%s[%d] [%s] error: format invalid", format, idx, line) - } - if l < 4 { - rule = append(rule, make([]string, 4-l)...) - } - if ruleName == "MATCH" { - l = 2 - } - if l >= 3 { - l = 3 - payload = rule[1] - } - target = rule[l-1] - params = rule[l:] + tp, payload, target, params := RC.ParseRulePayload(line, true) + if target == "" { + return nil, fmt.Errorf("%s[%d] [%s] error: format invalid", format, idx, line) } + if _, ok := proxies[target]; !ok { - if ruleName != "SUB-RULE" { + if tp != "SUB-RULE" { return nil, fmt.Errorf("%s[%d] [%s] error: proxy [%s] not found", format, idx, line, target) } else if _, ok = subRules[target]; !ok { return nil, fmt.Errorf("%s[%d] [%s] error: sub-rule [%s] not found", format, idx, line, target) } } - params = trimArr(params) - parsed, parseErr := R.ParseRule(ruleName, payload, target, params, subRules) + parsed, parseErr := R.ParseRule(tp, payload, target, params, subRules) if parseErr != nil { return nil, fmt.Errorf("%s[%d] [%s] error: %s", format, idx, line, parseErr.Error()) } diff --git a/config/utils.go b/config/utils.go index 8ce3e8b8..c72c120d 100644 --- a/config/utils.go +++ b/config/utils.go @@ -6,19 +6,11 @@ import ( "net/netip" "os" "strconv" - "strings" "github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/common/structure" ) -func trimArr(arr []string) (r []string) { - for _, e := range arr { - r = append(r, strings.Trim(e, " ")) - } - return -} - // Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order. // Meanwhile, record the original index in the config file. // If loop is detected, return an error with location of loop. diff --git a/rules/common/base.go b/rules/common/base.go index ab53753e..33ab5a1f 100644 --- a/rules/common/base.go +++ b/rules/common/base.go @@ -34,22 +34,48 @@ func ParseParams(params []string) (isSrc bool, noResolve bool) { return } -func ParseRulePayload(ruleRaw string) (string, string, []string) { - item := strings.Split(ruleRaw, ",") - if len(item) == 1 { - return "", item[0], nil - } else if len(item) == 2 { - return item[0], item[1], nil - } else if len(item) > 2 { - // keep in sync with config/config.go [parseRules] - if item[0] == "NOT" || item[0] == "OR" || item[0] == "AND" || item[0] == "SUB-RULE" || item[0] == "DOMAIN-REGEX" || item[0] == "PROCESS-NAME-REGEX" || item[0] == "PROCESS-PATH-REGEX" { - return item[0], strings.Join(item[1:], ","), nil - } else { - return item[0], item[1], item[2:] +func trimArr(arr []string) (r []string) { + for _, e := range arr { + r = append(r, strings.Trim(e, " ")) + } + return +} + +// ParseRulePayload parse rule format like: +// `tp,payload,target(,params...)` or `tp,payload(,params...)` +// needTarget control the format contains `target` in string +func ParseRulePayload(ruleRaw string, needTarget bool) (tp, payload, target string, params []string) { + item := trimArr(strings.Split(ruleRaw, ",")) + tp = strings.ToUpper(item[0]) + if len(item) > 1 { + switch tp { + case "MATCH": + // MATCH doesn't contain payload and params + target = item[1] + case "NOT", "OR", "AND", "SUB-RULE", "DOMAIN-REGEX", "PROCESS-NAME-REGEX", "PROCESS-PATH-REGEX": + // some type of rules that has comma in payload and don't need params + if needTarget { + l := len(item) + target = item[l-1] // don't have params so target must at the end of slices + item = item[:l-1] // remove the target from slices + } + payload = strings.Join(item[1:], ",") + default: + payload = item[1] + if len(item) > 2 { + if needTarget { + target = item[2] + if len(item) > 3 { + params = item[3:] + } + } else { + params = item[2:] + } + } } } - return "", "", nil + return } type ParseRuleFunc func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (C.Rule, error) diff --git a/rules/logic/logic.go b/rules/logic/logic.go index c740d8de..e4f3817e 100644 --- a/rules/logic/logic.go +++ b/rules/logic/logic.go @@ -78,14 +78,14 @@ func (r Range) containRange(preStart, preEnd int) bool { } func (logic *Logic) payloadToRule(subPayload string, parseRule common.ParseRuleFunc) (C.Rule, error) { - tp, payload, param := common.ParseRulePayload(subPayload) + tp, payload, target, param := common.ParseRulePayload(subPayload, false) switch tp { case "MATCH", "SUB-RULE": return nil, fmt.Errorf("unsupported rule type [%s] on logic rule", tp) case "": return nil, fmt.Errorf("[%s] format is error", subPayload) } - return parseRule(tp, payload, "", param, nil) + return parseRule(tp, payload, target, param, nil) } func (logic *Logic) format(payload string) ([]Range, error) { diff --git a/rules/parser.go b/rules/parser.go index 675c52ec..6d8b3b8e 100644 --- a/rules/parser.go +++ b/rules/parser.go @@ -10,6 +10,10 @@ import ( ) func ParseRule(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error) { + if tp != "MATCH" && payload == "" { // only MATCH allowed doesn't contain payload + return nil, fmt.Errorf("missing subsequent parameters: %s", tp) + } + switch tp { case "DOMAIN": parsed = RC.NewDomain(payload, target) @@ -83,8 +87,6 @@ func ParseRule(tp, payload, target string, params []string, subRules map[string] case "MATCH": parsed = RC.NewMatch(target) parseErr = nil - case "": - parseErr = fmt.Errorf("missing subsequent parameters: %s", payload) default: parseErr = fmt.Errorf("unsupported rule type: %s", tp) } diff --git a/rules/provider/classical_strategy.go b/rules/provider/classical_strategy.go index 2505b301..497f9168 100644 --- a/rules/provider/classical_strategy.go +++ b/rules/provider/classical_strategy.go @@ -12,7 +12,7 @@ import ( type classicalStrategy struct { rules []C.Rule count int - parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) + parse common.ParseRuleFunc } func (c *classicalStrategy) Behavior() P.RuleBehavior { @@ -39,25 +39,26 @@ func (c *classicalStrategy) Reset() { } func (c *classicalStrategy) Insert(rule string) { - ruleType, rule, params := common.ParseRulePayload(rule) - r, err := c.parse(ruleType, rule, "", params) + r, err := c.payloadToRule(rule) if err != nil { - log.Warnln("parse classical rule error: %s", err.Error()) + log.Warnln("parse classical rule [%s] error: %s", rule, err.Error()) } else { c.rules = append(c.rules, r) c.count++ } } +func (c *classicalStrategy) payloadToRule(rule string) (C.Rule, error) { + tp, payload, target, params := common.ParseRulePayload(rule, false) + switch tp { + case "MATCH", "RULE-SET", "SUB-RULE": + return nil, fmt.Errorf("unsupported rule type on classical rule-set: %s", tp) + } + return c.parse(tp, payload, target, params, nil) +} + func (c *classicalStrategy) FinishInsert() {} -func NewClassicalStrategy(parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) *classicalStrategy { - return &classicalStrategy{rules: []C.Rule{}, parse: func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { - switch tp { - case "MATCH", "RULE-SET", "SUB-RULE": - return nil, fmt.Errorf("unsupported rule type on classical rule-set: %s", tp) - default: - return parse(tp, payload, target, params, nil) - } - }} +func NewClassicalStrategy(parse common.ParseRuleFunc) *classicalStrategy { + return &classicalStrategy{rules: []C.Rule{}, parse: parse} }