package expressions import ( "errors" "regexp" "strings" "github.com/Knetic/govaluate" "github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl" ) var ( numericalExpressionRegex = regexp.MustCompile(`^[0-9+\-/\W]+$`) unresolvedVariablesRegex = regexp.MustCompile(`(?:%7[B|b]|\{){2}([^}]+)(?:%7[D|d]|\}){2}["'\)\}]*`) ) // ContainsUnresolvedVariables returns an error with variable names if the passed // input contains unresolved {{}} variables. func ContainsUnresolvedVariables(items ...string) error { for _, data := range items { matches := unresolvedVariablesRegex.FindAllStringSubmatch(data, -1) if len(matches) == 0 { return nil } var unresolvedVariables []string for _, match := range matches { if len(match) < 2 { continue } // Skip if the match is an expression if numericalExpressionRegex.MatchString(match[1]) { continue } // or if it contains only literals (can be solved from expression engine) if hasLiteralsOnly(match[1]) { continue } unresolvedVariables = append(unresolvedVariables, match[1]) } if len(unresolvedVariables) > 0 { return errors.New("unresolved variables found: " + strings.Join(unresolvedVariables, ",")) } } return nil } // ContainsVariablesWithNames returns an error with variable names if the passed // input contains unresolved {{}} variables within the provided list func ContainsVariablesWithNames(names map[string]interface{}, items ...string) error { for _, data := range items { matches := unresolvedVariablesRegex.FindAllStringSubmatch(data, -1) if len(matches) == 0 { return nil } var unresolvedVariables []string for _, match := range matches { if len(match) < 2 { continue } matchName := match[1] // Skip if the match is an expression if numericalExpressionRegex.MatchString(matchName) { continue } // or if it contains only literals (can be solved from expression engine) if hasLiteralsOnly(match[1]) { continue } if _, ok := names[matchName]; !ok { unresolvedVariables = append(unresolvedVariables, matchName) } } if len(unresolvedVariables) > 0 { return errors.New("unresolved variables with values found: " + strings.Join(unresolvedVariables, ",")) } } return nil } // ContainsVariablesWithIgnoreList returns an error with variable names if the passed // input contains unresolved {{}} other than the ones listed in the ignore list func ContainsVariablesWithIgnoreList(skipNames map[string]interface{}, items ...string) error { var unresolvedVariables []string for _, data := range items { matches := unresolvedVariablesRegex.FindAllStringSubmatch(data, -1) if len(matches) == 0 { return nil } for _, match := range matches { if len(match) < 2 { continue } matchName := match[1] // Skip if the match is an expression if numericalExpressionRegex.MatchString(matchName) { continue } // or if it contains only literals (can be solved from expression engine) if hasLiteralsOnly(match[1]) { continue } if _, ok := skipNames[matchName]; ok { continue } unresolvedVariables = append(unresolvedVariables, matchName) } } if len(unresolvedVariables) > 0 { return errors.New("unresolved variables with values found: " + strings.Join(unresolvedVariables, ",")) } return nil } func hasLiteralsOnly(data string) bool { expr, err := govaluate.NewEvaluableExpressionWithFunctions(data, dsl.HelperFunctions) if err != nil { return false } if expr != nil { _, err = expr.Evaluate(nil) return err == nil } return true }