Compare commits

...
Author SHA1 Message Date
Mzack9999 316d28c924 Honor interval 2026-10-26 16:40:40 +01:00
Mzack9999 95824bc4bc Merge dev 2026-10-26 13:48:09 +01:00
Mzack9999 dfeb4c59ae Count upfront 2026-10-26 11:20:55 +01:00
Mzack9999 ee0c72cbca Fix review 2026-10-25 23:16:41 +01:00
Mzack9999 f609ea2182 Merge dev 2026-10-25 23:15:36 +01:00
Mzack9999 9cf87cc9b9 probe progress 2026-07-22 17:51:37 +04:00
2 changed files with 97 additions and 3 deletions

No files matched your search

+68 -3
View File
@@ -18,6 +18,10 @@ import (
syncutil "github.com/projectdiscovery/utils/sync"
)
// Minimum number of hosts that need httpx probing before we emit periodic
// progress lines. Keeps small scans unchanged.
const httpxProbeProgressMinTargets = 1000
// initializeTemplatesHTTPInput initializes the http form of input
// for any loaded http templates if input is in non-standard format.
func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
@@ -56,7 +60,24 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
if err != nil {
return nil, errors.Wrap(err, "could not create adaptive group")
}
var count atomic.Int32
var alive atomic.Int32
var probed atomic.Int32
// swg.Add blocks once the pool is full, so the probe loop below only
// returns when almost every probe is done. Count the hosts first so the
// progress ticker runs while the probes run.
var stop func()
if r.options.EnableProgressBar {
var probeTotal int
r.inputProvider.Iterate(func(value *contextargs.MetaInput) bool {
if !stringsutil.HasPrefixAny(value.Input, "http://", "https://") {
probeTotal++
}
return true
})
stop = r.startHTTPXProbeProgress(probeTotal, &probed, &alive)
}
r.inputProvider.Iterate(func(value *contextargs.MetaInput) bool {
if stringsutil.HasPrefixAny(value.Input, "http://", "https://") {
return true
@@ -71,19 +92,63 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
swg.Add()
go func(target *contextargs.MetaInput) {
defer swg.Done()
defer probed.Add(1)
// a pinned target is probed through its ip, and each ip keeps its
// own result, as servers of one host may serve different schemes
ctx := contextargs.WithCustomIP(context.Background(), target)
if result := utils.ProbeURLWithContext(ctx, target.Input, httpxClient); result != "" {
count.Add(1)
alive.Add(1)
_ = hm.Set(input.ProbeKey(target.Input, target.CustomIP), []byte(result))
}
}(value)
return true
})
swg.Wait()
if stop != nil {
stop()
}
r.Logger.Info().Msgf("Found %d URL from httpx", count.Load())
r.Logger.Info().Msgf("Found %d URL from httpx", alive.Load())
return hm, nil
}
// startHTTPXProbeProgress optionally logs probe progress for large input sets
// when -stats is enabled. Returns a stop func that waits for the ticker goroutine
// to exit, or nil when progress is inactive.
func (r *Runner) startHTTPXProbeProgress(probeTotal int, probed, alive *atomic.Int32) func() {
if probeTotal < httpxProbeProgressMinTargets || !r.options.EnableProgressBar {
return nil
}
// like the stats ticker, an interval of zero or less disables periodic output
interval := time.Duration(r.options.StatsInterval) * time.Second
if interval <= 0 {
return nil
}
r.Logger.Info().Msgf("httpx probe progress enabled for %d hosts (interval %s)", probeTotal, interval)
done := make(chan struct{})
exited := make(chan struct{})
ticker := time.NewTicker(interval)
go func() {
defer close(exited)
defer ticker.Stop()
for {
select {
case <-done:
return
case <-ticker.C:
r.Logger.Info().Msgf(
"[httpx] probe progress: %d/%d (alive: %d)",
probed.Load(), probeTotal, alive.Load(),
)
}
}
}()
return func() {
close(done)
<-exited
}
}
+29
View File
@@ -0,0 +1,29 @@
package runner
import (
"sync/atomic"
"testing"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/stretchr/testify/require"
)
func TestHTTPXProbeProgressRespectsDisabledInterval(t *testing.T) {
var probed, alive atomic.Int32
for _, interval := range []int{0, -1} {
options := types.DefaultOptions()
options.EnableProgressBar = true
options.StatsInterval = interval
r := &Runner{options: options, Logger: gologger.DefaultLogger}
require.Nil(t, r.startHTTPXProbeProgress(httpxProbeProgressMinTargets, &probed, &alive), "interval %d", interval)
}
options := types.DefaultOptions()
options.EnableProgressBar = true
options.StatsInterval = 5
r := &Runner{options: options, Logger: gologger.DefaultLogger}
stop := r.startHTTPXProbeProgress(httpxProbeProgressMinTargets, &probed, &alive)
require.NotNil(t, stop)
stop()
}