github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/http/request_generator.go (about)

     1  package http
     2  
     3  import (
     4  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
     5  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
     6  )
     7  
     8  // requestGenerator generates requests sequentially based on various
     9  // configurations for a http request template.
    10  //
    11  // If payload values are present, an iterator is created for the payload
    12  // values. Paths and Raw requests are supported as base input, so
    13  // it will automatically select between them based on the template.
    14  type requestGenerator struct {
    15  	currentIndex     int
    16  	currentPayloads  map[string]interface{}
    17  	okCurrentPayload bool
    18  	request          *Request
    19  	options          *protocols.ExecutorOptions
    20  	payloadIterator  *generators.Iterator
    21  	interactshURLs   []string
    22  	onceFlow         map[string]struct{}
    23  }
    24  
    25  // LeaveDefaultPorts skips normalization of default standard ports
    26  var LeaveDefaultPorts = false
    27  
    28  // newGenerator creates a new request generator instance
    29  func (request *Request) newGenerator(disablePayloads bool) *requestGenerator {
    30  	generator := &requestGenerator{
    31  		request:  request,
    32  		options:  request.options,
    33  		onceFlow: make(map[string]struct{}),
    34  	}
    35  
    36  	if len(request.Payloads) > 0 && !disablePayloads {
    37  		generator.payloadIterator = request.generator.NewIterator()
    38  	}
    39  	return generator
    40  }
    41  
    42  // nextValue returns the next path or the next raw request depending on user input
    43  // It returns false if all the inputs have been exhausted by the generator instance.
    44  func (r *requestGenerator) nextValue() (value string, payloads map[string]interface{}, result bool) {
    45  	// Iterate each payload sequentially for each request path/raw
    46  	//
    47  	// If the sequence has finished for the current payload values
    48  	// then restart the sequence from the beginning and move on to the next payloads values
    49  	// otherwise use the last request.
    50  	var sequence []string
    51  	switch {
    52  	case len(r.request.Path) > 0:
    53  		sequence = r.request.Path
    54  	case len(r.request.Raw) > 0:
    55  		sequence = r.request.Raw
    56  	default:
    57  		return "", nil, false
    58  	}
    59  
    60  	hasPayloadIterator := r.payloadIterator != nil
    61  
    62  	if hasPayloadIterator && r.currentPayloads == nil {
    63  		r.currentPayloads, r.okCurrentPayload = r.payloadIterator.Value()
    64  	}
    65  
    66  	var request string
    67  	var shouldContinue bool
    68  	if nextRequest, nextIndex, found := r.findNextIteration(sequence, r.currentIndex); found {
    69  		r.currentIndex = nextIndex + 1
    70  		request = nextRequest
    71  		shouldContinue = true
    72  	} else {
    73  		// if found is false which happens at end of iteration of reqData(path or raw request)
    74  		// try again from start with index 0
    75  		if nextRequest, nextIndex, found := r.findNextIteration(sequence, 0); found && hasPayloadIterator {
    76  			r.currentIndex = nextIndex + 1
    77  			request = nextRequest
    78  			shouldContinue = true
    79  		}
    80  	}
    81  
    82  	if shouldContinue {
    83  		if r.hasMarker(request, Once) {
    84  			r.applyMark(request, Once)
    85  		}
    86  		if hasPayloadIterator {
    87  			return request, generators.MergeMaps(r.currentPayloads), r.okCurrentPayload
    88  		}
    89  		// next should return a copy of payloads and not pointer to payload to avoid data race
    90  		return request, generators.MergeMaps(r.currentPayloads), true
    91  	} else {
    92  		return "", nil, false
    93  	}
    94  }
    95  
    96  // findNextIteration iterates and returns next Request(path or raw request)
    97  // at end of each iteration payload is incremented
    98  func (r *requestGenerator) findNextIteration(sequence []string, index int) (string, int, bool) {
    99  	for i, request := range sequence[index:] {
   100  		if r.wasMarked(request, Once) {
   101  			// if request contains flowmark i.e `@once` and is marked skip it
   102  			continue
   103  		}
   104  		return request, index + i, true
   105  
   106  	}
   107  	// move on to next payload if current payload is applied/returned for all Requests(path or raw request)
   108  	if r.payloadIterator != nil {
   109  		r.currentPayloads, r.okCurrentPayload = r.payloadIterator.Value()
   110  	}
   111  	return "", 0, false
   112  }
   113  
   114  // applyMark marks given request i.e blacklist request
   115  func (r *requestGenerator) applyMark(request string, mark flowMark) {
   116  	switch mark {
   117  	case Once:
   118  		r.onceFlow[request] = struct{}{}
   119  	}
   120  
   121  }
   122  
   123  // wasMarked checks if request is marked using request blacklist
   124  func (r *requestGenerator) wasMarked(request string, mark flowMark) bool {
   125  	switch mark {
   126  	case Once:
   127  		_, ok := r.onceFlow[request]
   128  		return ok
   129  	}
   130  	return false
   131  }
   132  
   133  // hasMarker returns true if request has a marker (ex: @once which means request should only be executed once)
   134  func (r *requestGenerator) hasMarker(request string, mark flowMark) bool {
   135  	fo, hasOverrides := parseFlowAnnotations(request)
   136  	return hasOverrides && fo == mark
   137  }