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 }