150 lines
4.4 KiB
Go
Raw Permalink Normal View History

2021-09-19 16:26:47 +05:30
package markdown
import (
"bytes"
"os"
"path/filepath"
"strings"
2024-12-13 01:55:27 +03:00
"github.com/google/uuid"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format"
fileutil "github.com/projectdiscovery/utils/file"
stringsutil "github.com/projectdiscovery/utils/strings"
)
const indexFileName = "index.md"
const extension = ".md"
type Exporter struct {
directory string
options *Options
}
2021-09-07 17:31:46 +03:00
// Options contains the configuration options for GitHub issue tracker client
type Options struct {
// Directory is the directory to export found results to
Directory string `yaml:"directory"`
OmitRaw bool `yaml:"omit-raw"`
SortMode string `yaml:"sort-mode"`
}
2021-09-19 16:26:47 +05:30
// New creates a new markdown exporter integration client based on options.
func New(options *Options) (*Exporter, error) {
directory := options.Directory
if options.Directory == "" {
dir, err := os.Getwd()
if err != nil {
return nil, err
}
directory = dir
}
_ = os.MkdirAll(directory, 0755)
// index generation header
dataHeader := util.CreateTableHeader("Hostname/IP", "Finding", "Severity")
err := os.WriteFile(filepath.Join(directory, indexFileName), []byte(dataHeader), 0644)
if err != nil {
return nil, err
}
return &Exporter{options: options, directory: directory}, nil
}
2021-09-19 16:26:47 +05:30
// Export exports a passed result event to markdown
func (exporter *Exporter) Export(event *output.ResultEvent) error {
// index file generation
file, err := os.OpenFile(filepath.Join(exporter.directory, indexFileName), os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer func() {
_ = file.Close()
}()
filename := createFileName(event)
// If the sort mode is set to severity, host, or template, then we need to get a safe version of the name for a
// subdirectory to store the file in.
// This will allow us to sort the files into subdirectories based on the sort mode. The subdirectory will need to
// be created if it does not exist.
fileUrl := filename
subdirectory := ""
switch exporter.options.SortMode {
case "severity":
subdirectory = event.Info.SeverityHolder.Severity.String()
case "host":
subdirectory = event.Host
case "template":
subdirectory = event.TemplateID
}
if subdirectory != "" {
// Sanitize the subdirectory name to remove any characters that are not allowed in a directory name
subdirectory = sanitizeFilename(subdirectory)
// Prepend the subdirectory name to the filename for the fileUrl
fileUrl = filepath.Join(subdirectory, filename)
// Create the subdirectory if it does not exist
if err = fileutil.CreateFolders(filepath.Join(exporter.directory, subdirectory)); err != nil {
gologger.Warning().Msgf("Could not create subdirectory for markdown report: %s", err)
}
}
host := util.CreateLink(event.Host, fileUrl)
finding := event.TemplateID + " " + event.MatcherName
severity := event.Info.SeverityHolder.Severity.String()
_, err = file.WriteString(util.CreateTableRow(host, finding, severity))
if err != nil {
return err
}
dataBuilder := &bytes.Buffer{}
dataBuilder.WriteString(util.CreateHeading3(format.Summary(event)))
dataBuilder.WriteString("\n")
dataBuilder.WriteString(util.CreateHorizontalLine())
dataBuilder.WriteString(format.CreateReportDescription(event, util.MarkdownFormatter{}, exporter.options.OmitRaw))
data := dataBuilder.Bytes()
return os.WriteFile(filepath.Join(exporter.directory, subdirectory, filename), data, 0644)
}
func createFileName(event *output.ResultEvent) string {
filenameBuilder := &strings.Builder{}
2021-03-22 14:28:29 +05:30
filenameBuilder.WriteString(event.TemplateID)
filenameBuilder.WriteString("-")
2024-12-13 01:55:27 +03:00
filenameBuilder.WriteString(event.Host)
filenameBuilder.WriteString("-")
filenameBuilder.WriteString(uuid.NewString())
2021-09-26 18:20:05 +05:30
var suffix string
if event.MatcherName != "" {
2021-09-26 18:20:05 +05:30
suffix = event.MatcherName
} else if event.ExtractorName != "" {
2021-09-26 18:20:05 +05:30
suffix = event.ExtractorName
}
if suffix != "" {
filenameBuilder.WriteRune('-')
filenameBuilder.WriteString(event.MatcherName)
}
filenameBuilder.WriteString(extension)
return sanitizeFilename(filenameBuilder.String())
}
2021-06-05 18:01:08 +05:30
// Close closes the exporter after operation
func (exporter *Exporter) Close() error {
2021-06-05 18:01:08 +05:30
return nil
}
func sanitizeFilename(filename string) string {
if len(filename) > 256 {
filename = filename[0:255]
}
return stringsutil.ReplaceAll(filename, "_", "?", "/", ">", "|", ":", ";", "*", "<", "\"", "'", " ")
}