github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/common/expressions/expressions.go (about)

     1  package expressions
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/Knetic/govaluate"
     7  
     8  	"github.com/projectdiscovery/nuclei/v2/pkg/operators/common/dsl"
     9  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/marker"
    10  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/replacer"
    11  	stringsutil "github.com/projectdiscovery/utils/strings"
    12  )
    13  
    14  // Eval compiles the given expression and evaluate it with the given values preserving the return type
    15  func Eval(expression string, values map[string]interface{}) (interface{}, error) {
    16  	compiled, err := govaluate.NewEvaluableExpressionWithFunctions(expression, dsl.HelperFunctions)
    17  	if err != nil {
    18  		return nil, err
    19  	}
    20  	return compiled.Evaluate(values)
    21  }
    22  
    23  // Evaluate checks if the match contains a dynamic variable, for each
    24  // found one we will check if it's an expression and can
    25  // be compiled, it will be evaluated and the results will be returned.
    26  //
    27  // The provided keys from finalValues will be used as variable names
    28  // for substitution inside the expression.
    29  func Evaluate(data string, base map[string]interface{}) (string, error) {
    30  	return evaluate(data, base)
    31  }
    32  
    33  // EvaluateByte checks if the match contains a dynamic variable, for each
    34  // found one we will check if it's an expression and can
    35  // be compiled, it will be evaluated and the results will be returned.
    36  //
    37  // The provided keys from finalValues will be used as variable names
    38  // for substitution inside the expression.
    39  func EvaluateByte(data []byte, base map[string]interface{}) ([]byte, error) {
    40  	finalData, err := evaluate(string(data), base)
    41  	return []byte(finalData), err
    42  }
    43  
    44  func evaluate(data string, base map[string]interface{}) (string, error) {
    45  	// replace simple placeholders (key => value) MarkerOpen + key + MarkerClose and General + key + General to value
    46  	data = replacer.Replace(data, base)
    47  
    48  	// expressions can be:
    49  	// - simple: containing base values keys (variables)
    50  	// - complex: containing helper functions [ + variables]
    51  	// literals like {{2+2}} are not considered expressions
    52  	expressions := FindExpressions(data, marker.ParenthesisOpen, marker.ParenthesisClose, base)
    53  	for _, expression := range expressions {
    54  		// replace variable placeholders with base values
    55  		expression = replacer.Replace(expression, base)
    56  		// turns expressions (either helper functions+base values or base values)
    57  		compiled, err := govaluate.NewEvaluableExpressionWithFunctions(expression, dsl.HelperFunctions)
    58  		if err != nil {
    59  			continue
    60  		}
    61  		result, err := compiled.Evaluate(base)
    62  		if err != nil {
    63  			continue
    64  		}
    65  		// replace incrementally
    66  		data = replacer.ReplaceOne(data, expression, result)
    67  	}
    68  	return data, nil
    69  }
    70  
    71  // maxIterations to avoid infinite loop
    72  const maxIterations = 250
    73  
    74  func FindExpressions(data, OpenMarker, CloseMarker string, base map[string]interface{}) []string {
    75  	var (
    76  		iterations int
    77  		exps       []string
    78  	)
    79  	for {
    80  		// check if we reached the maximum number of iterations
    81  		if iterations > maxIterations {
    82  			break
    83  		}
    84  		iterations++
    85  		// attempt to find open markers
    86  		indexOpenMarker := strings.Index(data, OpenMarker)
    87  		// exits if not found
    88  		if indexOpenMarker < 0 {
    89  			break
    90  		}
    91  
    92  		indexOpenMarkerOffset := indexOpenMarker + len(OpenMarker)
    93  
    94  		shouldSearchCloseMarker := true
    95  		closeMarkerFound := false
    96  		innerData := data
    97  		var potentialMatch string
    98  		var indexCloseMarker, indexCloseMarkerOffset int
    99  		skip := indexOpenMarkerOffset
   100  		for shouldSearchCloseMarker {
   101  			// attempt to find close marker
   102  			indexCloseMarker = stringsutil.IndexAt(innerData, CloseMarker, skip)
   103  			// if no close markers are found exit
   104  			if indexCloseMarker < 0 {
   105  				shouldSearchCloseMarker = false
   106  				continue
   107  			}
   108  			indexCloseMarkerOffset = indexCloseMarker + len(CloseMarker)
   109  
   110  			potentialMatch = innerData[indexOpenMarkerOffset:indexCloseMarker]
   111  			if isExpression(potentialMatch, base) {
   112  				closeMarkerFound = true
   113  				shouldSearchCloseMarker = false
   114  				exps = append(exps, potentialMatch)
   115  			} else {
   116  				skip = indexCloseMarkerOffset
   117  			}
   118  		}
   119  
   120  		if closeMarkerFound {
   121  			// move after the close marker
   122  			data = data[indexCloseMarkerOffset:]
   123  		} else {
   124  			// move after the open marker
   125  			data = data[indexOpenMarkerOffset:]
   126  		}
   127  	}
   128  	return exps
   129  }
   130  
   131  func isExpression(data string, base map[string]interface{}) bool {
   132  	if _, err := govaluate.NewEvaluableExpression(data); err == nil {
   133  		if stringsutil.ContainsAny(data, getFunctionsNames(base)...) {
   134  			return true
   135  		} else if stringsutil.ContainsAny(data, dsl.FunctionNames...) {
   136  			return true
   137  		}
   138  		return false
   139  	}
   140  	_, err := govaluate.NewEvaluableExpressionWithFunctions(data, dsl.HelperFunctions)
   141  	return err == nil
   142  }
   143  
   144  func getFunctionsNames(m map[string]interface{}) []string {
   145  	keys := make([]string, 0, len(m))
   146  	for k := range m {
   147  		keys = append(keys, k)
   148  	}
   149  	return keys
   150  }