github.com/TeaOSLab/EdgeNode@v1.3.8/internal/waf/rule.go (about)

     1  package waf
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"encoding/binary"
     7  	"errors"
     8  	"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
     9  	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/filterconfigs"
    10  	"github.com/TeaOSLab/EdgeNode/internal/re"
    11  	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
    12  	"github.com/TeaOSLab/EdgeNode/internal/utils/runes"
    13  	"github.com/TeaOSLab/EdgeNode/internal/waf/checkpoints"
    14  	"github.com/TeaOSLab/EdgeNode/internal/waf/injectionutils"
    15  	"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
    16  	"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
    17  	"github.com/TeaOSLab/EdgeNode/internal/waf/values"
    18  	"github.com/iwind/TeaGo/lists"
    19  	"github.com/iwind/TeaGo/maps"
    20  	"github.com/iwind/TeaGo/types"
    21  	stringutil "github.com/iwind/TeaGo/utils/string"
    22  	"net"
    23  	"reflect"
    24  	"regexp"
    25  	"sort"
    26  	"strings"
    27  )
    28  
    29  var singleParamRegexp = regexp.MustCompile(`^\${[\w.-]+}$`)
    30  
    31  // Rule waf rule under rule set
    32  type Rule struct {
    33  	Id int64
    34  
    35  	Description       string         `yaml:"description" json:"description"`
    36  	Param             string         `yaml:"param" json:"param"` // such as ${arg.name} or ${args}, can be composite as ${arg.firstName}${arg.lastName}
    37  	ParamFilters      []*ParamFilter `yaml:"paramFilters" json:"paramFilters"`
    38  	Operator          RuleOperator   `yaml:"operator" json:"operator"` // such as contains, gt,  ...
    39  	Value             string         `yaml:"value" json:"value"`       // compared value
    40  	IsCaseInsensitive bool           `yaml:"isCaseInsensitive" json:"isCaseInsensitive"`
    41  	CheckpointOptions map[string]any `yaml:"checkpointOptions" json:"checkpointOptions"`
    42  	Priority          int            `yaml:"priority" json:"priority"`
    43  
    44  	checkpointFinder func(prefix string) checkpoints.CheckpointInterface
    45  
    46  	singleParam      string                          // real param after prefix
    47  	singleCheckpoint checkpoints.CheckpointInterface // if is single check point
    48  
    49  	multipleCheckpoints map[string]checkpoints.CheckpointInterface
    50  
    51  	isIP    bool
    52  	ipValue net.IP
    53  
    54  	ipRangeListValue *values.IPRangeList
    55  	stringValues     []string
    56  	stringValueRunes [][]rune
    57  	ipList           *values.StringList
    58  
    59  	floatValue float64
    60  
    61  	reg       *re.Regexp
    62  	cacheLife utils.CacheLife
    63  }
    64  
    65  func NewRule() *Rule {
    66  	return &Rule{}
    67  }
    68  
    69  func (this *Rule) Init() error {
    70  	// operator
    71  	switch this.Operator {
    72  	case RuleOperatorGt:
    73  		this.floatValue = types.Float64(this.Value)
    74  	case RuleOperatorGte:
    75  		this.floatValue = types.Float64(this.Value)
    76  	case RuleOperatorLt:
    77  		this.floatValue = types.Float64(this.Value)
    78  	case RuleOperatorLte:
    79  		this.floatValue = types.Float64(this.Value)
    80  	case RuleOperatorEq:
    81  		this.floatValue = types.Float64(this.Value)
    82  	case RuleOperatorNeq:
    83  		this.floatValue = types.Float64(this.Value)
    84  	case RuleOperatorContainsAny, RuleOperatorContainsAll, RuleOperatorContainsAnyWord, RuleOperatorContainsAllWords, RuleOperatorNotContainsAnyWord:
    85  		this.stringValues = []string{}
    86  		if len(this.Value) > 0 {
    87  			var lines = strings.Split(this.Value, "\n")
    88  			for _, line := range lines {
    89  				line = strings.TrimSpace(line)
    90  				if len(line) > 0 {
    91  					if this.IsCaseInsensitive {
    92  						this.stringValues = append(this.stringValues, strings.ToLower(line))
    93  					} else {
    94  						this.stringValues = append(this.stringValues, line)
    95  					}
    96  				}
    97  			}
    98  			if this.Operator == RuleOperatorContainsAnyWord || this.Operator == RuleOperatorContainsAllWords || this.Operator == RuleOperatorNotContainsAnyWord {
    99  				sort.Strings(this.stringValues)
   100  			}
   101  
   102  			this.stringValueRunes = [][]rune{}
   103  			for _, line := range this.stringValues {
   104  				this.stringValueRunes = append(this.stringValueRunes, []rune(line))
   105  			}
   106  		}
   107  	case RuleOperatorMatch:
   108  		var v = this.Value
   109  		if this.IsCaseInsensitive && !strings.HasPrefix(v, "(?i)") {
   110  			v = "(?i)" + v
   111  		}
   112  
   113  		v = this.unescape(v)
   114  
   115  		reg, err := re.Compile(v)
   116  		if err != nil {
   117  			return err
   118  		}
   119  		this.reg = reg
   120  	case RuleOperatorNotMatch:
   121  		var v = this.Value
   122  		if this.IsCaseInsensitive && !strings.HasPrefix(v, "(?i)") {
   123  			v = "(?i)" + v
   124  		}
   125  
   126  		v = this.unescape(v)
   127  
   128  		reg, err := re.Compile(v)
   129  		if err != nil {
   130  			return err
   131  		}
   132  		this.reg = reg
   133  	case RuleOperatorEqIP, RuleOperatorGtIP, RuleOperatorGteIP, RuleOperatorLtIP, RuleOperatorLteIP:
   134  		this.ipValue = net.ParseIP(this.Value)
   135  		this.isIP = this.ipValue != nil
   136  
   137  		if !this.isIP {
   138  			return errors.New("value should be a valid ip")
   139  		}
   140  	case RuleOperatorInIPList:
   141  		this.ipList = values.ParseStringList(this.Value, true)
   142  	case RuleOperatorIPRange, RuleOperatorNotIPRange:
   143  		this.ipRangeListValue = values.ParseIPRangeList(this.Value)
   144  	case RuleOperatorWildcardMatch, RuleOperatorWildcardNotMatch:
   145  		var pieces = strings.Split(this.Value, "*")
   146  		for index, piece := range pieces {
   147  			pieces[index] = regexp.QuoteMeta(piece)
   148  		}
   149  		var pattern = strings.Join(pieces, "(.*)")
   150  		var expr = "^" + pattern + "$"
   151  		if this.IsCaseInsensitive {
   152  			expr = "(?i)" + expr
   153  		}
   154  		reg, err := re.Compile(expr)
   155  		if err != nil {
   156  			return err
   157  		}
   158  		this.reg = reg
   159  	}
   160  
   161  	if singleParamRegexp.MatchString(this.Param) {
   162  		var param = this.Param[2 : len(this.Param)-1]
   163  		var pieces = strings.SplitN(param, ".", 2)
   164  		var prefix = pieces[0]
   165  		if len(pieces) == 1 {
   166  			this.singleParam = ""
   167  		} else {
   168  			this.singleParam = pieces[1]
   169  		}
   170  
   171  		if this.checkpointFinder != nil {
   172  			var checkpoint = this.checkpointFinder(prefix)
   173  			if checkpoint == nil {
   174  				return errors.New("no check point '" + prefix + "' found")
   175  			}
   176  			this.singleCheckpoint = checkpoint
   177  			this.Priority = checkpoint.Priority()
   178  
   179  			this.cacheLife = checkpoint.CacheLife()
   180  		} else {
   181  			var checkpoint = checkpoints.FindCheckpoint(prefix)
   182  			if checkpoint == nil {
   183  				return errors.New("no check point '" + prefix + "' found")
   184  			}
   185  			checkpoint.Init()
   186  			this.singleCheckpoint = checkpoint
   187  			this.Priority = checkpoint.Priority()
   188  
   189  			this.cacheLife = checkpoint.CacheLife()
   190  		}
   191  
   192  		return nil
   193  	}
   194  
   195  	this.multipleCheckpoints = map[string]checkpoints.CheckpointInterface{}
   196  	var err error = nil
   197  	configutils.ParseVariables(this.Param, func(varName string) (value string) {
   198  		var pieces = strings.SplitN(varName, ".", 2)
   199  		var prefix = pieces[0]
   200  		if this.checkpointFinder != nil {
   201  			var checkpoint = this.checkpointFinder(prefix)
   202  			if checkpoint == nil {
   203  				err = errors.New("no check point '" + prefix + "' found")
   204  			} else {
   205  				this.multipleCheckpoints[prefix] = checkpoint
   206  				this.Priority = checkpoint.Priority()
   207  
   208  				if this.cacheLife <= 0 || checkpoint.CacheLife() < this.cacheLife {
   209  					this.cacheLife = checkpoint.CacheLife()
   210  				}
   211  			}
   212  		} else {
   213  			var checkpoint = checkpoints.FindCheckpoint(prefix)
   214  			if checkpoint == nil {
   215  				err = errors.New("no check point '" + prefix + "' found")
   216  			} else {
   217  				checkpoint.Init()
   218  				this.multipleCheckpoints[prefix] = checkpoint
   219  				this.Priority = checkpoint.Priority()
   220  
   221  				this.cacheLife = checkpoint.CacheLife()
   222  			}
   223  		}
   224  
   225  		return ""
   226  	})
   227  
   228  	return err
   229  }
   230  
   231  func (this *Rule) MatchRequest(req requests.Request) (b bool, hasRequestBody bool, err error) {
   232  	if this.singleCheckpoint != nil {
   233  		value, hasCheckedRequestBody, err, _ := this.singleCheckpoint.RequestValue(req, this.singleParam, this.CheckpointOptions, this.Id)
   234  		if hasCheckedRequestBody {
   235  			hasRequestBody = true
   236  		}
   237  		if err != nil {
   238  			return false, hasRequestBody, err
   239  		}
   240  
   241  		// execute filters
   242  		if len(this.ParamFilters) > 0 {
   243  			value = this.execFilter(value)
   244  		}
   245  
   246  		// if is composed checkpoint, we just returns true or false
   247  		if this.singleCheckpoint.IsComposed() {
   248  			return types.Bool(value), hasRequestBody, nil
   249  		}
   250  
   251  		return this.Test(value), hasRequestBody, nil
   252  	}
   253  
   254  	var value = configutils.ParseVariables(this.Param, func(varName string) (value string) {
   255  		var pieces = strings.SplitN(varName, ".", 2)
   256  		var prefix = pieces[0]
   257  		point, ok := this.multipleCheckpoints[prefix]
   258  		if !ok {
   259  			return ""
   260  		}
   261  
   262  		if len(pieces) == 1 {
   263  			value1, hasCheckRequestBody, err1, _ := point.RequestValue(req, "", this.CheckpointOptions, this.Id)
   264  			if hasCheckRequestBody {
   265  				hasRequestBody = true
   266  			}
   267  			if err1 != nil {
   268  				err = err1
   269  			}
   270  			return this.stringifyValue(value1)
   271  		}
   272  
   273  		value1, hasCheckRequestBody, err1, _ := point.RequestValue(req, pieces[1], this.CheckpointOptions, this.Id)
   274  		if hasCheckRequestBody {
   275  			hasRequestBody = true
   276  		}
   277  		if err1 != nil {
   278  			err = err1
   279  		}
   280  		return this.stringifyValue(value1)
   281  	})
   282  
   283  	if err != nil {
   284  		return false, hasRequestBody, err
   285  	}
   286  
   287  	return this.Test(value), hasRequestBody, nil
   288  }
   289  
   290  func (this *Rule) MatchResponse(req requests.Request, resp *requests.Response) (b bool, hasRequestBody bool, err error) {
   291  	if this.singleCheckpoint != nil {
   292  		// if is request param
   293  		if this.singleCheckpoint.IsRequest() {
   294  			value, hasCheckRequestBody, err, _ := this.singleCheckpoint.RequestValue(req, this.singleParam, this.CheckpointOptions, this.Id)
   295  			if hasCheckRequestBody {
   296  				hasRequestBody = true
   297  			}
   298  			if err != nil {
   299  				return false, hasRequestBody, err
   300  			}
   301  
   302  			// execute filters
   303  			if len(this.ParamFilters) > 0 {
   304  				value = this.execFilter(value)
   305  			}
   306  
   307  			return this.Test(value), hasRequestBody, nil
   308  		}
   309  
   310  		// response param
   311  		value, hasCheckRequestBody, err, _ := this.singleCheckpoint.ResponseValue(req, resp, this.singleParam, this.CheckpointOptions, this.Id)
   312  		if hasCheckRequestBody {
   313  			hasRequestBody = true
   314  		}
   315  		if err != nil {
   316  			return false, hasRequestBody, err
   317  		}
   318  
   319  		// if is composed checkpoint, we just returns true or false
   320  		if this.singleCheckpoint.IsComposed() {
   321  			return types.Bool(value), hasRequestBody, nil
   322  		}
   323  
   324  		return this.Test(value), hasRequestBody, nil
   325  	}
   326  
   327  	var value = configutils.ParseVariables(this.Param, func(varName string) (value string) {
   328  		var pieces = strings.SplitN(varName, ".", 2)
   329  		var prefix = pieces[0]
   330  		point, ok := this.multipleCheckpoints[prefix]
   331  		if !ok {
   332  			return ""
   333  		}
   334  
   335  		if len(pieces) == 1 {
   336  			if point.IsRequest() {
   337  				value1, hasCheckRequestBody, err1, _ := point.RequestValue(req, "", this.CheckpointOptions, this.Id)
   338  				if hasCheckRequestBody {
   339  					hasRequestBody = true
   340  				}
   341  				if err1 != nil {
   342  					err = err1
   343  				}
   344  				return this.stringifyValue(value1)
   345  			} else {
   346  				value1, hasCheckRequestBody, err1, _ := point.ResponseValue(req, resp, "", this.CheckpointOptions, this.Id)
   347  				if hasCheckRequestBody {
   348  					hasRequestBody = true
   349  				}
   350  				if err1 != nil {
   351  					err = err1
   352  				}
   353  				return this.stringifyValue(value1)
   354  			}
   355  		}
   356  
   357  		if point.IsRequest() {
   358  			value1, hasCheckRequestBody, err1, _ := point.RequestValue(req, pieces[1], this.CheckpointOptions, this.Id)
   359  			if hasCheckRequestBody {
   360  				hasRequestBody = true
   361  			}
   362  			if err1 != nil {
   363  				err = err1
   364  			}
   365  			return this.stringifyValue(value1)
   366  		} else {
   367  			value1, hasCheckRequestBody, err1, _ := point.ResponseValue(req, resp, pieces[1], this.CheckpointOptions, this.Id)
   368  			if hasCheckRequestBody {
   369  				hasRequestBody = true
   370  			}
   371  			if err1 != nil {
   372  				err = err1
   373  			}
   374  			return this.stringifyValue(value1)
   375  		}
   376  	})
   377  
   378  	if err != nil {
   379  		return false, hasRequestBody, err
   380  	}
   381  
   382  	return this.Test(value), hasRequestBody, nil
   383  }
   384  
   385  func (this *Rule) Test(value any) bool {
   386  	// operator
   387  	switch this.Operator {
   388  	case RuleOperatorGt:
   389  		return types.Float64(value) > this.floatValue
   390  	case RuleOperatorGte:
   391  		return types.Float64(value) >= this.floatValue
   392  	case RuleOperatorLt:
   393  		return types.Float64(value) < this.floatValue
   394  	case RuleOperatorLte:
   395  		return types.Float64(value) <= this.floatValue
   396  	case RuleOperatorEq:
   397  		return types.Float64(value) == this.floatValue
   398  	case RuleOperatorNeq:
   399  		return types.Float64(value) != this.floatValue
   400  	case RuleOperatorEqString:
   401  		if this.IsCaseInsensitive {
   402  			return strings.EqualFold(this.stringifyValue(value), this.Value)
   403  		} else {
   404  			return this.stringifyValue(value) == this.Value
   405  		}
   406  	case RuleOperatorNeqString:
   407  		if this.IsCaseInsensitive {
   408  			return !strings.EqualFold(this.stringifyValue(value), this.Value)
   409  		} else {
   410  			return this.stringifyValue(value) != this.Value
   411  		}
   412  	case RuleOperatorMatch, RuleOperatorWildcardMatch:
   413  		if value == nil {
   414  			value = ""
   415  		}
   416  
   417  		// strings
   418  		stringList, ok := value.([]string)
   419  		if ok {
   420  			for _, s := range stringList {
   421  				if utils.MatchStringCache(this.reg, s, this.cacheLife) {
   422  					return true
   423  				}
   424  			}
   425  			return false
   426  		}
   427  
   428  		// bytes list
   429  		byteSlices, ok := value.([][]byte)
   430  		if ok {
   431  			for _, byteSlice := range byteSlices {
   432  				if utils.MatchBytesCache(this.reg, byteSlice, this.cacheLife) {
   433  					return true
   434  				}
   435  			}
   436  			return false
   437  		}
   438  
   439  		// bytes
   440  		byteSlice, ok := value.([]byte)
   441  		if ok {
   442  			return utils.MatchBytesCache(this.reg, byteSlice, this.cacheLife)
   443  		}
   444  
   445  		// string
   446  		return utils.MatchStringCache(this.reg, this.stringifyValue(value), this.cacheLife)
   447  	case RuleOperatorNotMatch, RuleOperatorWildcardNotMatch:
   448  		if value == nil {
   449  			value = ""
   450  		}
   451  		stringList, ok := value.([]string)
   452  		if ok {
   453  			for _, s := range stringList {
   454  				if utils.MatchStringCache(this.reg, s, this.cacheLife) {
   455  					return false
   456  				}
   457  			}
   458  			return true
   459  		}
   460  
   461  		// bytes list
   462  		byteSlices, ok := value.([][]byte)
   463  		if ok {
   464  			for _, byteSlice := range byteSlices {
   465  				if utils.MatchBytesCache(this.reg, byteSlice, this.cacheLife) {
   466  					return false
   467  				}
   468  			}
   469  			return true
   470  		}
   471  
   472  		// bytes
   473  		byteSlice, ok := value.([]byte)
   474  		if ok {
   475  			return !utils.MatchBytesCache(this.reg, byteSlice, this.cacheLife)
   476  		}
   477  
   478  		return !utils.MatchStringCache(this.reg, this.stringifyValue(value), this.cacheLife)
   479  	case RuleOperatorContains:
   480  		if types.IsSlice(value) {
   481  			_, isBytes := value.([]byte)
   482  			if !isBytes {
   483  				var ok = false
   484  				lists.Each(value, func(k int, v any) {
   485  					if this.stringifyValue(v) == this.Value {
   486  						ok = true
   487  					}
   488  				})
   489  				return ok
   490  			}
   491  		}
   492  		if types.IsMap(value) {
   493  			var lowerValue = ""
   494  			if this.IsCaseInsensitive {
   495  				lowerValue = strings.ToLower(this.Value)
   496  			}
   497  			for _, v := range maps.NewMap(value) {
   498  				if this.IsCaseInsensitive {
   499  					if strings.ToLower(this.stringifyValue(v)) == lowerValue {
   500  						return true
   501  					}
   502  				} else {
   503  					if this.stringifyValue(v) == this.Value {
   504  						return true
   505  					}
   506  				}
   507  			}
   508  			return false
   509  		}
   510  
   511  		if this.IsCaseInsensitive {
   512  			return strings.Contains(strings.ToLower(this.stringifyValue(value)), strings.ToLower(this.Value))
   513  		} else {
   514  			return strings.Contains(this.stringifyValue(value), this.Value)
   515  		}
   516  	case RuleOperatorNotContains:
   517  		if this.IsCaseInsensitive {
   518  			return !strings.Contains(strings.ToLower(this.stringifyValue(value)), strings.ToLower(this.Value))
   519  		} else {
   520  			return !strings.Contains(this.stringifyValue(value), this.Value)
   521  		}
   522  	case RuleOperatorPrefix:
   523  		if this.IsCaseInsensitive {
   524  			var s = this.stringifyValue(value)
   525  			var sl = len(s)
   526  			var vl = len(this.Value)
   527  			if sl < vl {
   528  				return false
   529  			}
   530  			s = s[:vl]
   531  			return strings.HasPrefix(strings.ToLower(s), strings.ToLower(this.Value))
   532  		} else {
   533  			return strings.HasPrefix(this.stringifyValue(value), this.Value)
   534  		}
   535  	case RuleOperatorSuffix:
   536  		if this.IsCaseInsensitive {
   537  			var s = this.stringifyValue(value)
   538  			var sl = len(s)
   539  			var vl = len(this.Value)
   540  			if sl < vl {
   541  				return false
   542  			}
   543  			s = s[sl-vl:]
   544  			return strings.HasSuffix(strings.ToLower(s), strings.ToLower(this.Value))
   545  		} else {
   546  			return strings.HasSuffix(this.stringifyValue(value), this.Value)
   547  		}
   548  	case RuleOperatorContainsAny:
   549  		var stringValue = this.stringifyValue(value)
   550  		if this.IsCaseInsensitive {
   551  			stringValue = strings.ToLower(stringValue)
   552  		}
   553  		if len(stringValue) > 0 && len(this.stringValues) > 0 {
   554  			for _, v := range this.stringValues {
   555  				if strings.Contains(stringValue, v) {
   556  					return true
   557  				}
   558  			}
   559  		}
   560  		return false
   561  	case RuleOperatorContainsAll:
   562  		var stringValue = this.stringifyValue(value)
   563  		if this.IsCaseInsensitive {
   564  			stringValue = strings.ToLower(stringValue)
   565  		}
   566  		if len(stringValue) > 0 && len(this.stringValues) > 0 {
   567  			for _, v := range this.stringValues {
   568  				if !strings.Contains(stringValue, v) {
   569  					return false
   570  				}
   571  			}
   572  			return true
   573  		}
   574  		return false
   575  	case RuleOperatorContainsAnyWord:
   576  		return runes.ContainsAnyWordRunes(this.stringifyValue(value), this.stringValueRunes, this.IsCaseInsensitive)
   577  	case RuleOperatorContainsAllWords:
   578  		return runes.ContainsAllWords(this.stringifyValue(value), this.stringValues, this.IsCaseInsensitive)
   579  	case RuleOperatorNotContainsAnyWord:
   580  		return !runes.ContainsAnyWordRunes(this.stringifyValue(value), this.stringValueRunes, this.IsCaseInsensitive)
   581  	case RuleOperatorContainsSQLInjection, RuleOperatorContainsSQLInjectionStrictly:
   582  		if value == nil {
   583  			return false
   584  		}
   585  		var isStrict = this.Operator == RuleOperatorContainsSQLInjectionStrictly
   586  		switch xValue := value.(type) {
   587  		case []string:
   588  			for _, v := range xValue {
   589  				if injectionutils.DetectSQLInjectionCache(v, isStrict, this.cacheLife) {
   590  					return true
   591  				}
   592  			}
   593  			return false
   594  		case [][]byte:
   595  			for _, v := range xValue {
   596  				if injectionutils.DetectSQLInjectionCache(string(v), isStrict, this.cacheLife) {
   597  					return true
   598  				}
   599  			}
   600  			return false
   601  		default:
   602  			return injectionutils.DetectSQLInjectionCache(this.stringifyValue(value), isStrict, this.cacheLife)
   603  		}
   604  	case RuleOperatorContainsXSS, RuleOperatorContainsXSSStrictly:
   605  		if value == nil {
   606  			return false
   607  		}
   608  		var isStrict = this.Operator == RuleOperatorContainsXSSStrictly
   609  		switch xValue := value.(type) {
   610  		case []string:
   611  			for _, v := range xValue {
   612  				if injectionutils.DetectXSSCache(v, isStrict, this.cacheLife) {
   613  					return true
   614  				}
   615  			}
   616  			return false
   617  		case [][]byte:
   618  			for _, v := range xValue {
   619  				if injectionutils.DetectXSSCache(string(v), isStrict, this.cacheLife) {
   620  					return true
   621  				}
   622  			}
   623  			return false
   624  		default:
   625  			return injectionutils.DetectXSSCache(this.stringifyValue(value), isStrict, this.cacheLife)
   626  		}
   627  	case RuleOperatorContainsBinary:
   628  		data, _ := base64.StdEncoding.DecodeString(this.stringifyValue(this.Value))
   629  		if this.IsCaseInsensitive {
   630  			return bytes.Contains(bytes.ToUpper([]byte(this.stringifyValue(value))), bytes.ToUpper(data))
   631  		} else {
   632  			return bytes.Contains([]byte(this.stringifyValue(value)), data)
   633  		}
   634  	case RuleOperatorNotContainsBinary:
   635  		data, _ := base64.StdEncoding.DecodeString(this.stringifyValue(this.Value))
   636  		if this.IsCaseInsensitive {
   637  			return !bytes.Contains(bytes.ToUpper([]byte(this.stringifyValue(value))), bytes.ToUpper(data))
   638  		} else {
   639  			return !bytes.Contains([]byte(this.stringifyValue(value)), data)
   640  		}
   641  	case RuleOperatorHasKey:
   642  		if types.IsSlice(value) {
   643  			var index = types.Int(this.Value)
   644  			if index < 0 {
   645  				return false
   646  			}
   647  			return reflect.ValueOf(value).Len() > index
   648  		} else if types.IsMap(value) {
   649  			var m = maps.NewMap(value)
   650  			if this.IsCaseInsensitive {
   651  				var lowerValue = strings.ToLower(this.Value)
   652  				for k := range m {
   653  					if strings.ToLower(k) == lowerValue {
   654  						return true
   655  					}
   656  				}
   657  			} else {
   658  				return m.Has(this.Value)
   659  			}
   660  		} else {
   661  			return false
   662  		}
   663  
   664  	case RuleOperatorVersionGt:
   665  		return stringutil.VersionCompare(this.Value, types.String(value)) > 0
   666  	case RuleOperatorVersionLt:
   667  		return stringutil.VersionCompare(this.Value, types.String(value)) < 0
   668  	case RuleOperatorVersionRange:
   669  		if strings.Contains(this.Value, ",") {
   670  			var versions = strings.SplitN(this.Value, ",", 2)
   671  			var version1 = strings.TrimSpace(versions[0])
   672  			var version2 = strings.TrimSpace(versions[1])
   673  			if len(version1) > 0 && stringutil.VersionCompare(types.String(value), version1) < 0 {
   674  				return false
   675  			}
   676  			if len(version2) > 0 && stringutil.VersionCompare(types.String(value), version2) > 0 {
   677  				return false
   678  			}
   679  			return true
   680  		} else {
   681  			return stringutil.VersionCompare(types.String(value), this.Value) >= 0
   682  		}
   683  	case RuleOperatorEqIP:
   684  		var ip = net.ParseIP(types.String(value))
   685  		if ip == nil {
   686  			return false
   687  		}
   688  		return this.isIP && ip.Equal(this.ipValue)
   689  	case RuleOperatorGtIP:
   690  		var ip = net.ParseIP(types.String(value))
   691  		if ip == nil {
   692  			return false
   693  		}
   694  		return this.isIP && bytes.Compare(ip, this.ipValue) > 0
   695  	case RuleOperatorGteIP:
   696  		var ip = net.ParseIP(types.String(value))
   697  		if ip == nil {
   698  			return false
   699  		}
   700  		return this.isIP && bytes.Compare(ip, this.ipValue) >= 0
   701  	case RuleOperatorLtIP:
   702  		var ip = net.ParseIP(types.String(value))
   703  		if ip == nil {
   704  			return false
   705  		}
   706  		return this.isIP && bytes.Compare(ip, this.ipValue) < 0
   707  	case RuleOperatorLteIP:
   708  		var ip = net.ParseIP(types.String(value))
   709  		if ip == nil {
   710  			return false
   711  		}
   712  		return this.isIP && bytes.Compare(ip, this.ipValue) <= 0
   713  	case RuleOperatorIPRange:
   714  		return this.containsIP(value)
   715  	case RuleOperatorNotIPRange:
   716  		return !this.containsIP(value)
   717  	case RuleOperatorIPMod:
   718  		var pieces = strings.SplitN(this.Value, ",", 2)
   719  		if len(pieces) == 1 {
   720  			var rem = types.Int64(pieces[0])
   721  			return this.ipToInt64(net.ParseIP(types.String(value)))%10 == rem
   722  		}
   723  		var div = types.Int64(pieces[0])
   724  		if div == 0 {
   725  			return false
   726  		}
   727  		var rem = types.Int64(pieces[1])
   728  		return this.ipToInt64(net.ParseIP(types.String(value)))%div == rem
   729  	case RuleOperatorIPMod10:
   730  		return this.ipToInt64(net.ParseIP(types.String(value)))%10 == types.Int64(this.Value)
   731  	case RuleOperatorIPMod100:
   732  		return this.ipToInt64(net.ParseIP(types.String(value)))%100 == types.Int64(this.Value)
   733  	case RuleOperatorInIPList:
   734  		if this.ipList != nil {
   735  			return this.ipList.Contains(types.String(value))
   736  		}
   737  		return false
   738  	}
   739  	return false
   740  }
   741  
   742  func (this *Rule) IsSingleCheckpoint() bool {
   743  	return this.singleCheckpoint != nil
   744  }
   745  
   746  func (this *Rule) SetCheckpointFinder(finder func(prefix string) checkpoints.CheckpointInterface) {
   747  	this.checkpointFinder = finder
   748  }
   749  
   750  var unescapeChars = [][2]string{
   751  	{`\s`, `(\s|%09|%0A|\+)`},
   752  	{`\(`, `(\(|%28)`},
   753  	{`=`, `(=|%3D)`},
   754  	{`<`, `(<|%3C)`},
   755  	{`\*`, `(\*|%2A)`},
   756  	{`\\`, `(\\|%2F)`},
   757  	{`!`, `(!|%21)`},
   758  	{`/`, `(/|%2F)`},
   759  	{`;`, `(;|%3B)`},
   760  	{`\+`, `(\+|%20)`},
   761  }
   762  
   763  func (this *Rule) unescape(v string) string {
   764  	// replace urlencoded characters
   765  
   766  	for _, c := range unescapeChars {
   767  		if !strings.Contains(v, c[0]) {
   768  			continue
   769  		}
   770  		var pieces = strings.Split(v, c[0])
   771  
   772  		// 修复piece中错误的\
   773  		for pieceIndex, piece := range pieces {
   774  			var l = len(piece)
   775  			if l == 0 {
   776  				continue
   777  			}
   778  			if piece[l-1] != '\\' {
   779  				continue
   780  			}
   781  
   782  			// 计算\的数量
   783  			var countBackSlashes = 0
   784  			for i := l - 1; i >= 0; i-- {
   785  				if piece[i] == '\\' {
   786  					countBackSlashes++
   787  				} else {
   788  					break
   789  				}
   790  			}
   791  			if countBackSlashes%2 == 1 {
   792  				// 去掉最后一个
   793  				pieces[pieceIndex] = piece[:len(piece)-1]
   794  			}
   795  		}
   796  
   797  		v = strings.Join(pieces, c[1])
   798  	}
   799  
   800  	return v
   801  }
   802  
   803  func (this *Rule) containsIP(value any) bool {
   804  	if this.ipRangeListValue == nil {
   805  		return false
   806  	}
   807  	return this.ipRangeListValue.Contains(types.String(value))
   808  }
   809  
   810  func (this *Rule) ipToInt64(ip net.IP) int64 {
   811  	if len(ip) == 0 {
   812  		return 0
   813  	}
   814  	if len(ip) == 16 {
   815  		return int64(binary.BigEndian.Uint32(ip[12:16]))
   816  	}
   817  	return int64(binary.BigEndian.Uint32(ip))
   818  }
   819  
   820  func (this *Rule) execFilter(value any) any {
   821  	var goNext bool
   822  	var err error
   823  
   824  	for _, filter := range this.ParamFilters {
   825  		filterInstance := filterconfigs.FindFilter(filter.Code)
   826  		if filterInstance == nil {
   827  			continue
   828  		}
   829  		value, goNext, err = filterInstance.Do(value, filter.Options)
   830  		if err != nil {
   831  			remotelogs.Error("WAF", "filter error: "+err.Error())
   832  			break
   833  		}
   834  		if !goNext {
   835  			break
   836  		}
   837  	}
   838  	return value
   839  }
   840  
   841  func (this *Rule) stringifyValue(value any) string {
   842  	if value == nil {
   843  		return ""
   844  	}
   845  	switch v := value.(type) {
   846  	case string:
   847  		return v
   848  	case []string:
   849  		return strings.Join(v, "")
   850  	case []byte:
   851  		return string(v)
   852  	case [][]byte:
   853  		var b = &bytes.Buffer{}
   854  		for _, vb := range v {
   855  			b.Write(vb)
   856  		}
   857  		return b.String()
   858  	default:
   859  		return types.String(v)
   860  	}
   861  }