Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions runner/md_output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package runner

import (
"fmt"
"net/http"
"strings"
)

func MarkdownHeader(r Result) string {
var b strings.Builder
b.WriteString("| URL | Status | Method | IP | Size | Words | Lines | Title | CDN |")
b.WriteString("\n")
b.WriteString("|---|---|---|---|---|---|---|---|---|")
b.WriteString("\n")

return b.String()
}

func (r Result) MarkdownRow(scanopts *ScanOptions) string {
var b strings.Builder

fmt.Fprintf(&b, "| %s | `%d %s` | `%s` | `%s` | %d | %d | %d |",
escapeMarkdown(r.URL),
r.StatusCode, http.StatusText(r.StatusCode),
r.Method,
r.HostIP,
r.ContentLength,
r.Words,
r.Lines)

if r.Title != "" {
fmt.Fprintf(&b, " %s |", escapeMarkdown(r.Title))
} else {
b.WriteString(" |")
}

if r.CDNName != "" {
fmt.Fprintf(&b, " `%s` |", r.CDNName)
} else {
b.WriteString(" |")
}

b.WriteString("\n")
return b.String()
}

func escapeMarkdown(s string) string {
replacer := strings.NewReplacer(
"|", "\\|",
"\n", " ",
)
return strings.TrimSpace(replacer.Replace(s))
}
2 changes: 2 additions & 0 deletions runner/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ type Options struct {
RespectHSTS bool
StoreResponse bool
JSONOutput bool
MarkDownOutput bool
CSVOutput bool
CSVOutputEncoding string
PdcpAuth string
Expand Down Expand Up @@ -478,6 +479,7 @@ func ParseOptions() *Options {
flagSet.BoolVar(&options.CSVOutput, "csv", false, "store output in csv format"),
flagSet.StringVarP(&options.CSVOutputEncoding, "csv-output-encoding", "csvo", "", "define output encoding"),
flagSet.BoolVarP(&options.JSONOutput, "json", "j", false, "store output in JSONL(ines) format"),
flagSet.BoolVarP(&options.MarkDownOutput, "markdown", "md", false, "store output in Markdown table format"),
flagSet.BoolVarP(&options.ResponseHeadersInStdout, "include-response-header", "irh", false, "include http response (headers) in JSON output (-json only)"),
flagSet.BoolVarP(&options.ResponseInStdout, "include-response", "irr", false, "include http request/response (headers + body) in JSON output (-json only)"),
flagSet.BoolVarP(&options.Base64ResponseInStdout, "include-response-base64", "irrb", false, "include base64 encoded http request/response in JSON output (-json only)"),
Expand Down
58 changes: 48 additions & 10 deletions runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,8 @@
}
}()

var plainFile, jsonFile, csvFile, indexFile, indexScreenshotFile *os.File
var plainFile, jsonFile, csvFile, mdFile, indexFile, indexScreenshotFile *os.File
markdownHeaderWritten := false // guard to prevent writing the header multiple times

if r.options.Output != "" && r.options.OutputAll {
plainFile = openOrCreateFile(r.options.Resume, r.options.Output)
Expand All @@ -830,11 +831,15 @@
defer func() {
_ = csvFile.Close()
}()
mdFile = openOrCreateFile(r.options.Resume, r.options.Output+".md")
defer func() {
_ = mdFile.Close()
}()
}

jsonOrCsv := (r.options.JSONOutput || r.options.CSVOutput)
jsonAndCsv := (r.options.JSONOutput && r.options.CSVOutput)
if r.options.Output != "" && plainFile == nil && !jsonOrCsv {
jsonOrCsvOrMD := (r.options.JSONOutput || r.options.CSVOutput || r.options.MarkDownOutput)
jsonAndCsvAndMD := (r.options.JSONOutput && r.options.CSVOutput && r.options.MarkDownOutput)
if r.options.Output != "" && plainFile == nil && !jsonOrCsvOrMD {
plainFile = openOrCreateFile(r.options.Resume, r.options.Output)
defer func() {
_ = plainFile.Close()
Expand All @@ -843,7 +848,7 @@

if r.options.Output != "" && r.options.JSONOutput && jsonFile == nil {
ext := ""
if jsonAndCsv {
if jsonAndCsvAndMD {
ext = ".json"
}
jsonFile = openOrCreateFile(r.options.Resume, r.options.Output+ext)
Expand All @@ -854,7 +859,7 @@

if r.options.Output != "" && r.options.CSVOutput && csvFile == nil {
ext := ""
if jsonAndCsv {
if jsonAndCsvAndMD {
ext = ".csv"
}
csvFile = openOrCreateFile(r.options.Resume, r.options.Output+ext)
Expand All @@ -863,6 +868,17 @@
}()
}

if r.options.Output != "" && r.options.MarkDownOutput && mdFile == nil {
ext := ""
if jsonAndCsvAndMD {
ext = ".md"
}
mdFile = openOrCreateFile(r.options.Resume, r.options.Output+ext)
defer func() {
_ = mdFile.Close()
}()
}

if r.options.CSVOutput {
outEncoding := strings.ToLower(r.options.CSVOutputEncoding)
switch outEncoding {
Expand All @@ -877,7 +893,7 @@
gologger.Fatal().Msgf("unknown csv output encoding: %s\n", r.options.CSVOutputEncoding)
}
headers := Result{}.CSVHeader()
if !r.options.OutputAll && !jsonAndCsv {
if !r.options.OutputAll && !jsonAndCsvAndMD {
gologger.Silent().Msgf("%s\n", headers)
}

Expand Down Expand Up @@ -1092,7 +1108,7 @@
}
}

if !r.options.DisableStdout && (!jsonOrCsv || jsonAndCsv || r.options.OutputAll) {
if !r.options.DisableStdout && (!jsonOrCsvOrMD || jsonAndCsvAndMD || r.options.OutputAll) {
gologger.Silent().Msgf("%s\n", resp.str)
}

Expand Down Expand Up @@ -1205,7 +1221,7 @@
if r.options.JSONOutput {
row := resp.JSON(&r.scanopts)

if !r.options.OutputAll && !jsonAndCsv {
if !r.options.OutputAll && !jsonAndCsvAndMD {
gologger.Silent().Msgf("%s\n", row)
}

Expand All @@ -1218,7 +1234,7 @@
if r.options.CSVOutput {
row := resp.CSVRow(&r.scanopts)

if !r.options.OutputAll && !jsonAndCsv {
if !r.options.OutputAll && !jsonAndCsvAndMD {
gologger.Silent().Msgf("%s\n", row)
}

Expand All @@ -1228,6 +1244,28 @@
}
}

if r.options.MarkDownOutput || r.options.OutputAll {
if !markdownHeaderWritten {
header := MarkdownHeader(resp)
if !r.options.OutputAll {
gologger.Silent().Msgf("%s", header)
}
if mdFile != nil {
mdFile.WriteString(header)

Check failure on line 1254 in runner/runner.go

View workflow job for this annotation

GitHub Actions / Lint Test

Error return value of `mdFile.WriteString` is not checked (errcheck)
}
markdownHeaderWritten = true
}

row := resp.MarkdownRow(&r.scanopts)

if !r.options.OutputAll {
gologger.Silent().Msgf("%s", row)
}
if mdFile != nil {
mdFile.WriteString(row)

Check failure on line 1265 in runner/runner.go

View workflow job for this annotation

GitHub Actions / Lint Test

Error return value of `mdFile.WriteString` is not checked (errcheck)
}
}

for _, nextStep := range nextSteps {
nextStep <- resp
}
Expand Down
Loading