Path: blob/dev/pkg/protocols/http/request_generator.go
2070 views
package http12import (3"github.com/projectdiscovery/nuclei/v3/pkg/protocols"4"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"5)67// requestGenerator generates requests sequentially based on various8// configurations for a http request template.9//10// If payload values are present, an iterator is created for the payload11// values. Paths and Raw requests are supported as base input, so12// it will automatically select between them based on the template.13type requestGenerator struct {14currentIndex int15currentPayloads map[string]interface{}16okCurrentPayload bool17request *Request18options *protocols.ExecutorOptions19payloadIterator *generators.Iterator20interactshURLs []string21onceFlow map[string]struct{}22}2324// LeaveDefaultPorts skips normalization of default standard ports25var LeaveDefaultPorts = false2627// newGenerator creates a new request generator instance28func (request *Request) newGenerator(disablePayloads bool) *requestGenerator {29generator := &requestGenerator{30request: request,31options: request.options,32onceFlow: make(map[string]struct{}),33}3435if len(request.Payloads) > 0 && !disablePayloads {36generator.payloadIterator = request.generator.NewIterator()37}38return generator39}4041// nextValue returns the next path or the next raw request depending on user input42// It returns false if all the inputs have been exhausted by the generator instance.43func (r *requestGenerator) nextValue() (value string, payloads map[string]interface{}, result bool) {44// Iterate each payload sequentially for each request path/raw45//46// If the sequence has finished for the current payload values47// then restart the sequence from the beginning and move on to the next payloads values48// otherwise use the last request.49var sequence []string50switch {51case len(r.request.Path) > 0:52sequence = r.request.Path53case len(r.request.Raw) > 0:54sequence = r.request.Raw55default:56return "", nil, false57}5859hasPayloadIterator := r.payloadIterator != nil6061if hasPayloadIterator && r.currentPayloads == nil {62r.currentPayloads, r.okCurrentPayload = r.payloadIterator.Value()63}6465var request string66var shouldContinue bool67if nextRequest, nextIndex, found := r.findNextIteration(sequence, r.currentIndex); found {68r.currentIndex = nextIndex + 169request = nextRequest70shouldContinue = true71} else {72// if found is false which happens at end of iteration of reqData(path or raw request)73// try again from start with index 074if nextRequest, nextIndex, found := r.findNextIteration(sequence, 0); found && hasPayloadIterator {75r.currentIndex = nextIndex + 176request = nextRequest77shouldContinue = true78}79}8081if shouldContinue {82if r.hasMarker(request, Once) {83r.applyMark(request, Once)84}85if hasPayloadIterator {86return request, generators.MergeMaps(r.currentPayloads), r.okCurrentPayload87}88// next should return a copy of payloads and not pointer to payload to avoid data race89return request, generators.MergeMaps(r.currentPayloads), true90} else {91return "", nil, false92}93}9495// findNextIteration iterates and returns next Request(path or raw request)96// at end of each iteration payload is incremented97func (r *requestGenerator) findNextIteration(sequence []string, index int) (string, int, bool) {98for i, request := range sequence[index:] {99if r.wasMarked(request, Once) {100// if request contains flowmark i.e `@once` and is marked skip it101continue102}103return request, index + i, true104105}106// move on to next payload if current payload is applied/returned for all Requests(path or raw request)107if r.payloadIterator != nil {108r.currentPayloads, r.okCurrentPayload = r.payloadIterator.Value()109}110return "", 0, false111}112113// applyMark marks given request i.e blacklist request114func (r *requestGenerator) applyMark(request string, mark flowMark) {115switch mark {116case Once:117r.onceFlow[request] = struct{}{}118}119120}121122// wasMarked checks if request is marked using request blacklist123func (r *requestGenerator) wasMarked(request string, mark flowMark) bool {124switch mark {125case Once:126_, ok := r.onceFlow[request]127return ok128}129return false130}131132// hasMarker returns true if request has a marker (ex: @once which means request should only be executed once)133func (r *requestGenerator) hasMarker(request string, mark flowMark) bool {134fo, hasOverrides := parseFlowAnnotations(request)135return hasOverrides && fo == mark136}137138// Remaining returns the number of requests that are still left to be139// generated (and therefore to be sent) by this generator.140func (r *requestGenerator) Remaining() int {141var sequence []string142switch {143case len(r.request.Path) > 0:144sequence = r.request.Path145case len(r.request.Raw) > 0:146sequence = r.request.Raw147default:148return 0149}150151remainingInCurrentPass := 0152for i := r.currentIndex; i < len(sequence); i++ {153if !r.hasMarker(sequence[i], Once) {154remainingInCurrentPass++155}156}157158if r.payloadIterator == nil {159return remainingInCurrentPass160}161162numRemainingPayloadSets := r.payloadIterator.Remaining()163totalValidInSequence := 0164for _, req := range sequence {165if !r.hasMarker(req, Once) {166totalValidInSequence++167}168}169170// Total remaining = remaining in current pass + (remaining payload sets * requests per full pass)171return remainingInCurrentPass + numRemainingPayloadSets*totalValidInSequence172}173174func (r *requestGenerator) Total() int {175var sequence []string176switch {177case len(r.request.Path) > 0:178sequence = r.request.Path179case len(r.request.Raw) > 0:180sequence = r.request.Raw181default:182return 0183}184185applicableRequests := 0186additionalRequests := 0187for _, request := range sequence {188if !r.hasMarker(request, Once) {189applicableRequests++190} else {191additionalRequests++192}193}194195if r.payloadIterator == nil {196return applicableRequests + additionalRequests197}198199return (applicableRequests * r.payloadIterator.Total()) + additionalRequests200}201202203