diff --git a/constant/provider/interface.go b/constant/provider/interface.go index b42bbe71..fb5efd87 100644 --- a/constant/provider/interface.go +++ b/constant/provider/interface.go @@ -73,17 +73,27 @@ type ProxyProvider interface { Version() uint32 } -// Rule Type +// RuleProvider interface +type RuleProvider interface { + Provider + Behavior() RuleBehavior + Match(*constant.Metadata) bool + ShouldResolveIP() bool + ShouldFindProcess() bool + AsRule(adaptor string) constant.Rule +} + +// Rule Behavior const ( - Domain RuleType = iota + Domain RuleBehavior = iota IPCIDR Classical ) -// RuleType defined -type RuleType int +// RuleBehavior defined +type RuleBehavior int -func (rt RuleType) String() string { +func (rt RuleBehavior) String() string { switch rt { case Domain: return "Domain" @@ -96,12 +106,20 @@ func (rt RuleType) String() string { } } -// RuleProvider interface -type RuleProvider interface { - Provider - Behavior() RuleType - Match(*constant.Metadata) bool - ShouldResolveIP() bool - ShouldFindProcess() bool - AsRule(adaptor string) constant.Rule +const ( + YamlRule RuleFormat = iota + TextRule +) + +type RuleFormat int + +func (rf RuleFormat) String() string { + switch rf { + case YamlRule: + return "YamlRule" + case TextRule: + return "TextRule" + default: + return "Unknown" + } } diff --git a/rules/provider/parse.go b/rules/provider/parse.go index 206bef10..6a001b50 100644 --- a/rules/provider/parse.go +++ b/rules/provider/parse.go @@ -14,6 +14,7 @@ type ruleProviderSchema struct { Behavior string `provider:"behavior"` Path string `provider:"path"` URL string `provider:"url,omitempty"` + Format string `provider:"format,omitempty"` Interval int `provider:"interval,omitempty"` } @@ -23,7 +24,7 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t if err := decoder.Decode(mapping, schema); err != nil { return nil, err } - var behavior P.RuleType + var behavior P.RuleBehavior switch schema.Behavior { case "domain": @@ -36,6 +37,17 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t return nil, fmt.Errorf("unsupported behavior type: %s", schema.Behavior) } + var format P.RuleFormat + + switch schema.Format { + case "", "yaml": + format = P.YamlRule + case "text": + format = P.TextRule + default: + return nil, fmt.Errorf("unsupported format type: %s", schema.Format) + } + path := C.Path.Resolve(schema.Path) var vehicle P.Vehicle switch schema.Type { @@ -47,5 +59,5 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type) } - return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle, parse), nil + return NewRuleSetProvider(name, behavior, format, time.Duration(uint(schema.Interval))*time.Second, vehicle, parse), nil } diff --git a/rules/provider/provider.go b/rules/provider/provider.go index 6b3c0290..a4271655 100644 --- a/rules/provider/provider.go +++ b/rules/provider/provider.go @@ -6,6 +6,7 @@ import ( "errors" "gopkg.in/yaml.v3" "runtime" + "strings" "time" "github.com/Dreamacro/clash/common/pool" @@ -20,7 +21,8 @@ var ( type ruleSetProvider struct { *resource.Fetcher[any] - behavior P.RuleType + behavior P.RuleBehavior + format P.RuleFormat strategy ruleStrategy } @@ -81,7 +83,7 @@ func (rp *ruleSetProvider) Update() error { return err } -func (rp *ruleSetProvider) Behavior() P.RuleType { +func (rp *ruleSetProvider) Behavior() P.RuleBehavior { return rp.behavior } @@ -105,6 +107,7 @@ func (rp *ruleSetProvider) MarshalJSON() ([]byte, error) { return json.Marshal( map[string]interface{}{ "behavior": rp.behavior.String(), + "format": rp.format.String(), "name": rp.Name(), "ruleCount": rp.strategy.Count(), "type": rp.Type().String(), @@ -113,10 +116,11 @@ func (rp *ruleSetProvider) MarshalJSON() ([]byte, error) { }) } -func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration, vehicle P.Vehicle, +func NewRuleSetProvider(name string, behavior P.RuleBehavior, format P.RuleFormat, interval time.Duration, vehicle P.Vehicle, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) P.RuleProvider { rp := &ruleSetProvider{ behavior: behavior, + format: format, } onUpdate := func(elm interface{}) { @@ -125,7 +129,7 @@ func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration } rp.strategy = newStrategy(behavior, parse) - rp.Fetcher = resource.NewFetcher(name, interval, vehicle, func(bytes []byte) (any, error) { return rulesParse(bytes, newStrategy(behavior, parse)) }, onUpdate) + rp.Fetcher = resource.NewFetcher(name, interval, vehicle, func(bytes []byte) (any, error) { return rulesParse(bytes, newStrategy(behavior, parse), format) }, onUpdate) wrapper := &RuleSetProvider{ rp, @@ -136,7 +140,7 @@ func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration return wrapper } -func newStrategy(behavior P.RuleType, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) ruleStrategy { +func newStrategy(behavior P.RuleBehavior, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) ruleStrategy { switch behavior { case P.Domain: strategy := NewDomainStrategy() @@ -154,7 +158,7 @@ func newStrategy(behavior P.RuleType, parse func(tp, payload, target string, par var ErrNoPayload = errors.New("file must have a `payload` field") -func rulesParse(buf []byte, strategy ruleStrategy) (any, error) { +func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (any, error) { strategy.Reset() schema := &RulePayload{} @@ -177,36 +181,54 @@ func rulesParse(buf []byte, strategy ruleStrategy) (any, error) { return nil, ErrNoPayload } } - firstLineBuffer.Write(line) - if firstLineLength == 0 { // find payload head - firstLineLength = firstLineBuffer.Len() - firstLineBuffer.WriteString(" - ''") // a test line + var str string + switch format { + case P.TextRule: + firstLineLength = -1 // don't return ErrNoPayload when read last line + str = string(line) + str = strings.TrimSpace(str) + if str[0] == '#' { // comment + continue + } + if strings.HasPrefix(str, "//") { // comment in Premium core + continue + } + case P.YamlRule: + if bytes.TrimSpace(line)[0] == '#' { // comment + continue + } + firstLineBuffer.Write(line) + if firstLineLength == 0 { // find payload head + firstLineLength = firstLineBuffer.Len() + firstLineBuffer.WriteString(" - ''") // a test line - err := yaml.Unmarshal(firstLineBuffer.Bytes(), schema) - firstLineBuffer.Truncate(firstLineLength) - if err == nil && (len(schema.Rules) > 0 || len(schema.Payload) > 0) { // found + err := yaml.Unmarshal(firstLineBuffer.Bytes(), schema) + firstLineBuffer.Truncate(firstLineLength) + if err == nil && (len(schema.Rules) > 0 || len(schema.Payload) > 0) { // found + continue + } + + // not found or err!=nil + firstLineBuffer.Truncate(0) + firstLineLength = 0 continue } - // not found or err!=nil - firstLineBuffer.Truncate(0) - firstLineLength = 0 - continue + // parse payload body + err := yaml.Unmarshal(firstLineBuffer.Bytes(), schema) + firstLineBuffer.Truncate(firstLineLength) + if err != nil { + continue + } + + if len(schema.Rules) > 0 { + str = schema.Rules[0] + } + if len(schema.Payload) > 0 { + str = schema.Payload[0] + } } - // parse payload body - err := yaml.Unmarshal(firstLineBuffer.Bytes(), schema) - firstLineBuffer.Truncate(firstLineLength) - if err != nil { - continue - } - var str string - if len(schema.Rules) > 0 { - str = schema.Rules[0] - } - if len(schema.Payload) > 0 { - str = schema.Payload[0] - } if str == "" { continue }