github.com/metacubex/mihomo@v1.18.5/rules/provider/provider.go (about)

     1  package provider
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"gopkg.in/yaml.v3"
     8  	"runtime"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/metacubex/mihomo/common/pool"
    13  	"github.com/metacubex/mihomo/component/resource"
    14  	C "github.com/metacubex/mihomo/constant"
    15  	P "github.com/metacubex/mihomo/constant/provider"
    16  )
    17  
    18  var (
    19  	ruleProviders = map[string]P.RuleProvider{}
    20  )
    21  
    22  type ruleSetProvider struct {
    23  	*resource.Fetcher[any]
    24  	behavior P.RuleBehavior
    25  	format   P.RuleFormat
    26  	strategy ruleStrategy
    27  }
    28  
    29  type RuleSetProvider struct {
    30  	*ruleSetProvider
    31  }
    32  
    33  type RulePayload struct {
    34  	/**
    35  	key: Domain or IP Cidr
    36  	value: Rule type or is empty
    37  	*/
    38  	Payload []string `yaml:"payload"`
    39  	Rules   []string `yaml:"rules"`
    40  }
    41  
    42  type ruleStrategy interface {
    43  	Match(metadata *C.Metadata) bool
    44  	Count() int
    45  	ShouldResolveIP() bool
    46  	ShouldFindProcess() bool
    47  	Reset()
    48  	Insert(rule string)
    49  	FinishInsert()
    50  }
    51  
    52  func RuleProviders() map[string]P.RuleProvider {
    53  	return ruleProviders
    54  }
    55  
    56  func SetRuleProvider(ruleProvider P.RuleProvider) {
    57  	if ruleProvider != nil {
    58  		ruleProviders[(ruleProvider).Name()] = ruleProvider
    59  	}
    60  }
    61  
    62  func (rp *ruleSetProvider) Type() P.ProviderType {
    63  	return P.Rule
    64  }
    65  
    66  func (rp *ruleSetProvider) Initial() error {
    67  	elm, err := rp.Fetcher.Initial()
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	rp.OnUpdate(elm)
    73  	return nil
    74  }
    75  
    76  func (rp *ruleSetProvider) Update() error {
    77  	elm, same, err := rp.Fetcher.Update()
    78  	if err == nil && !same {
    79  		rp.OnUpdate(elm)
    80  		return nil
    81  	}
    82  
    83  	return err
    84  }
    85  
    86  func (rp *ruleSetProvider) Behavior() P.RuleBehavior {
    87  	return rp.behavior
    88  }
    89  
    90  func (rp *ruleSetProvider) Match(metadata *C.Metadata) bool {
    91  	return rp.strategy != nil && rp.strategy.Match(metadata)
    92  }
    93  
    94  func (rp *ruleSetProvider) ShouldResolveIP() bool {
    95  	return rp.strategy.ShouldResolveIP()
    96  }
    97  
    98  func (rp *ruleSetProvider) ShouldFindProcess() bool {
    99  	return rp.strategy.ShouldFindProcess()
   100  }
   101  
   102  func (rp *ruleSetProvider) AsRule(adaptor string) C.Rule {
   103  	panic("implement me")
   104  }
   105  
   106  func (rp *ruleSetProvider) MarshalJSON() ([]byte, error) {
   107  	return json.Marshal(
   108  		map[string]interface{}{
   109  			"behavior":    rp.behavior.String(),
   110  			"format":      rp.format.String(),
   111  			"name":        rp.Name(),
   112  			"ruleCount":   rp.strategy.Count(),
   113  			"type":        rp.Type().String(),
   114  			"updatedAt":   rp.UpdatedAt,
   115  			"vehicleType": rp.VehicleType().String(),
   116  		})
   117  }
   118  
   119  func NewRuleSetProvider(name string, behavior P.RuleBehavior, format P.RuleFormat, interval time.Duration, vehicle P.Vehicle,
   120  	parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) P.RuleProvider {
   121  	rp := &ruleSetProvider{
   122  		behavior: behavior,
   123  		format:   format,
   124  	}
   125  
   126  	onUpdate := func(elm interface{}) {
   127  		strategy := elm.(ruleStrategy)
   128  		rp.strategy = strategy
   129  	}
   130  
   131  	rp.strategy = newStrategy(behavior, parse)
   132  	rp.Fetcher = resource.NewFetcher(name, interval, vehicle, func(bytes []byte) (any, error) { return rulesParse(bytes, newStrategy(behavior, parse), format) }, onUpdate)
   133  
   134  	wrapper := &RuleSetProvider{
   135  		rp,
   136  	}
   137  
   138  	final := func(provider *RuleSetProvider) { _ = rp.Fetcher.Destroy() }
   139  	runtime.SetFinalizer(wrapper, final)
   140  	return wrapper
   141  }
   142  
   143  func newStrategy(behavior P.RuleBehavior, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) ruleStrategy {
   144  	switch behavior {
   145  	case P.Domain:
   146  		strategy := NewDomainStrategy()
   147  		return strategy
   148  	case P.IPCIDR:
   149  		strategy := NewIPCidrStrategy()
   150  		return strategy
   151  	case P.Classical:
   152  		strategy := NewClassicalStrategy(parse)
   153  		return strategy
   154  	default:
   155  		return nil
   156  	}
   157  }
   158  
   159  var ErrNoPayload = errors.New("file must have a `payload` field")
   160  
   161  func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (any, error) {
   162  	strategy.Reset()
   163  
   164  	schema := &RulePayload{}
   165  
   166  	firstLineBuffer := pool.GetBuffer()
   167  	defer pool.PutBuffer(firstLineBuffer)
   168  	firstLineLength := 0
   169  
   170  	s := 0 // search start index
   171  	for s < len(buf) {
   172  		// search buffer for a new line.
   173  		line := buf[s:]
   174  		if i := bytes.IndexByte(line, '\n'); i >= 0 {
   175  			i += s
   176  			line = buf[s : i+1]
   177  			s = i + 1
   178  		} else {
   179  			s = len(buf)              // stop loop in next step
   180  			if firstLineLength == 0 { // no head or only one line body
   181  				return nil, ErrNoPayload
   182  			}
   183  		}
   184  		var str string
   185  		switch format {
   186  		case P.TextRule:
   187  			firstLineLength = -1 // don't return ErrNoPayload when read last line
   188  			str = string(line)
   189  			str = strings.TrimSpace(str)
   190  			if len(str) == 0 {
   191  				continue
   192  			}
   193  			if str[0] == '#' { // comment
   194  				continue
   195  			}
   196  			if strings.HasPrefix(str, "//") { // comment in Premium core
   197  				continue
   198  			}
   199  		case P.YamlRule:
   200  			trimLine := bytes.TrimSpace(line)
   201  			if len(trimLine) == 0 {
   202  				continue
   203  			}
   204  			if trimLine[0] == '#' { // comment
   205  				continue
   206  			}
   207  			firstLineBuffer.Write(line)
   208  			if firstLineLength == 0 { // find payload head
   209  				firstLineLength = firstLineBuffer.Len()
   210  				firstLineBuffer.WriteString("  - ''") // a test line
   211  
   212  				err := yaml.Unmarshal(firstLineBuffer.Bytes(), schema)
   213  				firstLineBuffer.Truncate(firstLineLength)
   214  				if err == nil && (len(schema.Rules) > 0 || len(schema.Payload) > 0) { // found
   215  					continue
   216  				}
   217  
   218  				// not found or err!=nil
   219  				firstLineBuffer.Truncate(0)
   220  				firstLineLength = 0
   221  				continue
   222  			}
   223  
   224  			// parse payload body
   225  			err := yaml.Unmarshal(firstLineBuffer.Bytes(), schema)
   226  			firstLineBuffer.Truncate(firstLineLength)
   227  			if err != nil {
   228  				continue
   229  			}
   230  
   231  			if len(schema.Rules) > 0 {
   232  				str = schema.Rules[0]
   233  			}
   234  			if len(schema.Payload) > 0 {
   235  				str = schema.Payload[0]
   236  			}
   237  		}
   238  
   239  		if str == "" {
   240  			continue
   241  		}
   242  
   243  		strategy.Insert(str)
   244  	}
   245  
   246  	strategy.FinishInsert()
   247  
   248  	return strategy, nil
   249  }