diff --git a/runner/runner.go b/runner/runner.go index 2ce2710e..87b50c83 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -7,6 +7,7 @@ import ( "encoding/csv" "encoding/json" "fmt" + "html/template" "io" "net" "net/http" @@ -28,8 +29,10 @@ import ( dsl "github.com/projectdiscovery/dsl" "github.com/projectdiscovery/httpx/common/customextract" "github.com/projectdiscovery/httpx/common/hashes/jarm" + "github.com/projectdiscovery/httpx/static" "github.com/projectdiscovery/mapcidr/asn" errorutil "github.com/projectdiscovery/utils/errors" + osutil "github.com/projectdiscovery/utils/os" "github.com/Mzack9999/gcache" "github.com/logrusorgru/aurora" @@ -603,12 +606,21 @@ func (r *Runner) RunEnumeration() { } // output routine - wgoutput := sizedwaitgroup.New(1) + wgoutput := sizedwaitgroup.New(2) wgoutput.Add() + output := make(chan Result) - go func(output chan Result) { + nextStep := make(chan Result) + + go func(output chan Result, nextSteps ...chan Result) { defer wgoutput.Done() + defer func() { + for _, nextStep := range nextSteps { + close(nextStep) + } + }() + var f, indexFile, indexScreenshotFile *os.File if r.options.Output != "" { @@ -860,8 +872,59 @@ func (r *Runner) RunEnumeration() { //nolint:errcheck // this method needs a small refactor to reduce complexity f.WriteString(row + "\n") } + + for _, nextStep := range nextSteps { + nextStep <- resp + } + } + }(output, nextStep) + + // HTML Summary + // - needs output of previous routine + // - separate goroutine due to incapability of go templates to render from file + wgoutput.Add() + go func(output chan Result) { + defer wgoutput.Done() + + if r.options.Screenshot { + screenshotHtmlPath := filepath.Join(r.options.StoreResponseDir, "screenshot", "screenshot.html") + screenshotHtml, err := os.Create(screenshotHtmlPath) + if err != nil { + gologger.Warning().Msgf("Could not create HTML file %s\n", err) + } + defer screenshotHtml.Close() + + templateMap := template.FuncMap{ + "safeURL": func(u string) template.URL { + if osutil.IsWindows() { + u = fmt.Sprintf("file:///%s", u) + } + return template.URL(u) + }, + } + tmpl, err := template. + New("screenshotTemplate"). + Funcs(templateMap). + Parse(static.HtmlTemplate) + if err != nil { + gologger.Warning().Msgf("Could not create HTML template: %v\n", err) + } + + if err = tmpl.Execute(screenshotHtml, struct { + Options Options + Output chan Result + }{ + Options: *r.options, + Output: output, + }); err != nil { + gologger.Warning().Msgf("Could not execute HTML template: %v\n", err) + } + } + + // fallthrough if anything is left in the buffer unblocks if screenshot is false + for range output { } - }(output) + }(nextStep) wg := sizedwaitgroup.New(r.options.Threads) diff --git a/static/html-summary.html b/static/html-summary.html new file mode 100644 index 00000000..9ca0aac3 --- /dev/null +++ b/static/html-summary.html @@ -0,0 +1,133 @@ + + + + + Screenshot Table + + + + + + + + + + + + + + {{ $ExtractTitle := .Options.ExtractTitle }} + {{ $OutputStatusCode := .Options.StatusCode }} + {{ $OutputContentLength := .Options.ContentLength }} + {{ $Favicon := .Options.Favicon }} + {{ $OutputResponseTime := .Options.OutputResponseTime }} + {{ $OutputLinesCount := .Options.OutputLinesCount }} + {{ $OutputWordsCount := .Options.OutputWordsCount }} + {{ $OutputServerHeader := .Options.OutputServerHeader }} + {{ $TechDetect := .Options.TechDetect }} + {{range .Output}} + {{if ne .ScreenshotPath ""}} + + + + + {{end}} + {{end}} + +
+ Response Info + + Screenshot +
+ + + + Screenshot + +
+ + + \ No newline at end of file diff --git a/static/static.go b/static/static.go new file mode 100644 index 00000000..82bf6598 --- /dev/null +++ b/static/static.go @@ -0,0 +1,8 @@ +package static + +import ( + _ "embed" +) + +//go:embed html-summary.html +var HtmlTemplate string