RES-84 # Improve Nuclei CLI interface (WIP)

* removed the generic isEmpty implementation
This commit is contained in:
forgedhallpass 2021-08-03 14:51:34 +03:00
parent 68a6d394e7
commit 2f162e859e
13 changed files with 52 additions and 155 deletions

View File

@ -123,7 +123,7 @@ func New(options *types.Options) (*Runner, error) {
os.Exit(0)
}
if (utils.IsEmpty(options.Templates) || !options.NewTemplates || (utils.IsEmpty(options.Targets, options.Target) && utils.IsNotEmpty(options.Stdin))) && options.UpdateTemplates {
if (len(options.Templates) == 0 || !options.NewTemplates || (options.Targets == "" && !options.Stdin && options.Target == "")) && options.UpdateTemplates {
os.Exit(0)
}
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
@ -211,7 +211,7 @@ func New(options *types.Options) (*Runner, error) {
// create project file if requested or load existing one
if options.Project {
var projectFileErr error
runner.projectFile, projectFileErr = projectfile.New(&projectfile.Options{Path: options.ProjectPath, Cleanup: utils.IsEmpty(options.ProjectPath)})
runner.projectFile, projectFileErr = projectfile.New(&projectfile.Options{Path: options.ProjectPath, Cleanup: utils.IsBlank(options.ProjectPath)})
if projectFileErr != nil {
return nil, projectFileErr
}
@ -354,10 +354,10 @@ func (r *Runner) RunEnumeration() error {
if r.interactsh != nil {
gologger.Info().Msgf("Using Interactsh Server %s", r.options.InteractshURL)
}
if utils.IsNotEmpty(store.Templates()) {
if len(store.Templates()) > 0 {
gologger.Info().Msgf("Templates loaded: %d (New: %d)", len(store.Templates()), r.countNewTemplates())
}
if utils.IsNotEmpty(store.Workflows()) {
if len(store.Workflows()) > 0 {
gologger.Info().Msgf("Workflows loaded: %d", len(store.Workflows()))
}
@ -367,8 +367,8 @@ func (r *Runner) RunEnumeration() error {
var unclusteredRequests int64
for _, template := range store.Templates() {
// workflows will dynamically adjust the totals while running, as
// it can't be know in advance which requests will be called
if utils.IsNotEmpty(template.Workflows) {
// it can't be known in advance which requests will be called
if len(template.Workflows) > 0 {
continue
}
unclusteredRequests += int64(template.TotalRequests) * r.inputCount
@ -419,7 +419,7 @@ func (r *Runner) RunEnumeration() error {
var totalRequests int64
for _, t := range finalTemplates {
if utils.IsNotEmpty(t.Workflows) {
if len(t.Workflows) > 0 {
continue
}
totalRequests += int64(t.TotalRequests) * r.inputCount

View File

@ -5,7 +5,6 @@ import (
"strings"
"github.com/projectdiscovery/nuclei/v2/internal/severity"
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
)
// TagFilter is used to filter nuclei templates for tag based execution
@ -46,7 +45,7 @@ func (tagFilter *TagFilter) Match(templateTags, templateAuthors []string, templa
return false, nil
}
if utils.IsNotEmpty(tagFilter.severities) {
if len(tagFilter.severities) > 0 {
if _, ok := tagFilter.severities[templateSeverity]; !ok {
return false, nil
}
@ -56,7 +55,7 @@ func (tagFilter *TagFilter) Match(templateTags, templateAuthors []string, templa
}
func isAuthorMatch(templateAuthors []string, tagFilter *TagFilter) bool {
if utils.IsEmpty(tagFilter.authors) {
if len(tagFilter.authors) == 0 {
return true
}
@ -70,7 +69,7 @@ func isAuthorMatch(templateAuthors []string, tagFilter *TagFilter) bool {
}
func isTagMatch(templateTags []string, tagFilter *TagFilter) bool {
if utils.IsEmpty(tagFilter.allowedTags) {
if len(tagFilter.allowedTags) == 0 {
return true
}
@ -101,7 +100,7 @@ func (tagFilter *TagFilter) MatchWithWorkflowTags(templateTags, templateAuthors
}
}
if utils.IsNotEmpty(workflowAllowedTagMap) { // TODO review, does not seem to make sense
if len(workflowAllowedTagMap) > 0 { // TODO review, does not seem to make sense
for _, templateTag := range templateTags {
if _, ok := workflowAllowedTagMap[templateTag]; !ok {
return false, nil
@ -109,7 +108,7 @@ func (tagFilter *TagFilter) MatchWithWorkflowTags(templateTags, templateAuthors
}
}
if utils.IsNotEmpty(tagFilter.authors) {
if len(tagFilter.authors) > 0 {
for _, templateAuthor := range templateAuthors {
if _, ok := tagFilter.authors[templateAuthor]; !ok {
return false, nil
@ -117,7 +116,7 @@ func (tagFilter *TagFilter) MatchWithWorkflowTags(templateTags, templateAuthors
}
}
if utils.IsNotEmpty(tagFilter.severities) {
if len(tagFilter.severities) > 0 {
if _, ok := tagFilter.severities[templateSeverity]; !ok {
return false, nil
}

View File

@ -10,7 +10,6 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/parsers"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
)
// Config contains the configuration options for the loader
@ -61,7 +60,7 @@ func New(config *Config) (*Store, error) {
}
// Handle a case with no templates or workflows, where we use base directory
if utils.IsEmpty(config.Templates, config.Workflows) {
if len(config.Templates) == 0 && len(config.Workflows) == 0 {
config.Templates = append(config.Templates, config.TemplatesDirectory)
}
store.finalTemplates = append(store.finalTemplates, config.Templates...)

View File

@ -1,10 +1,11 @@
package model
import (
"fmt"
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
"strings"
"github.com/projectdiscovery/nuclei/v2/internal/severity"
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
)
type Info struct {
@ -16,12 +17,14 @@ type Info struct {
SeverityHolder severity.SeverityHolder `yaml:"severity"`
}
// StringSlice represents a single (in-lined) or multiple string value(s).
// The unmarshaller does not automatically convert in-lined strings to []string, hence the interface{} type is required.
type StringSlice struct {
Value interface{}
}
func (stringSlice *StringSlice) IsEmpty() bool {
return utils.IsEmpty(stringSlice.Value)
return len(stringSlice.ToSlice()) == 0
}
func (stringSlice StringSlice) ToSlice() []string {
@ -32,8 +35,9 @@ func (stringSlice StringSlice) ToSlice() []string {
return value
case nil:
return []string{}
default:
panic(fmt.Sprintf("Unexpected StringSlice type: '%T'", value))
}
panic("Illegal State: StringSlice holds non-string value(s)")
}
func (stringSlice *StringSlice) UnmarshalYAML(unmarshal func(interface{}) error) error {
@ -44,7 +48,7 @@ func (stringSlice *StringSlice) UnmarshalYAML(unmarshal func(interface{}) error)
result := make([]string, len(marshalledSlice))
for _, value := range marshalledSlice {
result = append(result, strings.ToLower(strings.TrimSpace(value)))
result = append(result, strings.ToLower(strings.TrimSpace(value))) // TODO do we need to introduce RawStringSlice and/or NormalizedStringSlices?
}
stringSlice.Value = result
return nil
@ -65,7 +69,7 @@ func marshalStringToSlice(unmarshal func(interface{}) error) ([]string, error) {
var result []string
if len(marshalledValuesAsSlice) > 0 {
result = marshalledValuesAsSlice
} else if utils.IsNotEmpty(marshalledValueAsString) {
} else if utils.IsNotBlank(marshalledValueAsString) {
result = strings.Split(marshalledValueAsString, ",")
} else {
result = []string{}

View File

@ -28,7 +28,7 @@ func Load(templatePath string, isWorkflow bool, workflowTags []string, tagFilter
return false, validationError
}
if utils.IsNotEmpty(template.Workflows) {
if len(template.Workflows) > 0 {
if isWorkflow {
return true, nil // if a workflow is declared and this template is a workflow, then load
} else { //nolint:indent-error-flow,revive // preferred: readability and extensibility
@ -48,7 +48,7 @@ func isInfoMetadataMatch(tagFilter *filter.TagFilter, templateInfo *model.Info,
var match bool
var err error
if utils.IsEmpty(workflowTags) {
if len(workflowTags) == 0 {
match, err = tagFilter.Match(templateTags, templateAuthors, templateSeverity)
} else {
match, err = tagFilter.MatchWithWorkflowTags(templateTags, templateAuthors, templateSeverity, workflowTags)
@ -62,16 +62,15 @@ func isInfoMetadataMatch(tagFilter *filter.TagFilter, templateInfo *model.Info,
}
func validateMandatoryInfoFields(info *model.Info) error {
if utils.IsEmpty(&info) {
if &info == nil {
return fmt.Errorf(mandatoryFieldMissingTemplate, "info")
}
if utils.IsEmpty(&info.Name) {
if utils.IsBlank(info.Name) {
return fmt.Errorf(mandatoryFieldMissingTemplate, "name")
}
authors := info.Authors.ToSlice()
if utils.IsEmpty(&authors) {
if info.Authors.IsEmpty() {
return fmt.Errorf(mandatoryFieldMissingTemplate, "author")
}
return nil

View File

@ -62,7 +62,7 @@ func (i *Exporter) Export(event *output.ResultEvent) error {
sarifSeverity := getSarifSeverity(event)
var ruleName string
if utils.IsNotEmpty(event.Info.Name) {
if utils.IsNotBlank(event.Info.Name) {
ruleName = event.Info.Name
}
@ -74,7 +74,7 @@ func (i *Exporter) Export(event *output.ResultEvent) error {
}
var ruleDescription string
if utils.IsNotEmpty(event.Info.Description) {
if utils.IsNotBlank(event.Info.Description) {
ruleDescription = event.Info.Description
}

View File

@ -8,7 +8,6 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/types"
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
)
// Summary returns a formatted built one line summary of the event
@ -125,11 +124,12 @@ func MarkdownDescription(event *output.ResultEvent) string {
}
}
referenceValue := event.Info.Reference.Value
if utils.IsNotEmpty(referenceValue) {
reference := event.Info.Reference
if !reference.IsEmpty() {
builder.WriteString("\nReference: \n")
switch value := referenceValue.(type) { // TODO revisit
// TODO remove the code duplication: format.go <-> jira.go
switch value := reference.Value.(type) {
case string:
if !strings.HasPrefix(value, "-") {
builder.WriteString("- ")

View File

@ -15,7 +15,6 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/trackers/github"
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/trackers/gitlab"
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/trackers/jira"
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
)
// Options is a configuration file for nuclei reporting module

View File

@ -12,7 +12,6 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/format"
"github.com/projectdiscovery/nuclei/v2/pkg/types"
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
)
// Integration is a client for a issue tracker integration
@ -186,11 +185,13 @@ func jiraFormatDescription(event *output.ResultEvent) string {
builder.WriteString("\n{code}\n")
}
}
referenceValue := event.Info.Reference.Value
if utils.IsNotEmpty(referenceValue) {
reference := event.Info.Reference
if !reference.IsEmpty() {
builder.WriteString("\nReference: \n")
switch v := referenceValue.(type) { // TODO revisit
// TODO remove the code duplication: format.go <-> jira.go
switch v := reference.Value.(type) {
case string:
if !strings.HasPrefix(v, "-") {
builder.WriteString("- ")

View File

@ -39,10 +39,10 @@ func Parse(filePath string, options protocols.ExecuterOptions) (*Template, error
return nil, err
}
if utils.IsEmpty(template.Info.Name) {
if utils.IsBlank(template.Info.Name) {
return nil, errors.New("no template name field provided")
}
if utils.IsEmpty(template.Info.Authors) {
if template.Info.Authors.IsEmpty() {
return nil, errors.New("no template author field provided")
}
@ -52,12 +52,12 @@ func Parse(filePath string, options protocols.ExecuterOptions) (*Template, error
options.TemplatePath = filePath
// If no requests, and it is also not a workflow, return error.
if utils.IsEmpty(template.RequestsDNS, template.RequestsHTTP, template.RequestsFile, template.RequestsNetwork, template.RequestsHeadless, template.Workflows) {
if len(template.RequestsDNS)+len(template.RequestsHTTP)+len(template.RequestsFile)+len(template.RequestsNetwork)+len(template.RequestsHeadless)+len(template.Workflows) == 0 {
return nil, fmt.Errorf("no requests defined for %s", template.ID)
}
// Compile the workflow request
if utils.IsNotEmpty(template.Workflows) {
if len(template.Workflows) > 0 {
compiled := &template.Workflow
compileWorkflow(&options, compiled, options.WorkflowLoader)

View File

@ -4,7 +4,6 @@ import (
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v2/pkg/model"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
"github.com/projectdiscovery/nuclei/v2/pkg/workflows"
)
@ -45,13 +44,13 @@ func parseWorkflow(workflow *workflows.WorkflowTemplate, options *protocols.Exec
return nil
}
// parseWorkflowTemplate parses a workflow template creating an executor
// parseWorkflowTemplate parses a workflow template creating an executer
func parseWorkflowTemplate(workflow *workflows.WorkflowTemplate, options *protocols.ExecuterOptions, loader model.WorkflowLoader, noValidate bool) error {
var paths []string
workflowTags := workflow.Tags.ToSlice()
if utils.IsNotEmpty(workflowTags) {
paths = loader.ListTags(workflowTags)
workflowTags := workflow.Tags
if !workflowTags.IsEmpty() {
paths = loader.ListTags(workflowTags.ToSlice())
} else {
paths = loader.ListTemplates([]string{workflow.Template}, noValidate)
}
@ -76,7 +75,7 @@ func parseWorkflowTemplate(workflow *workflows.WorkflowTemplate, options *protoc
continue
}
if template.Executer == nil {
gologger.Warning().Msgf("Could not parse workflow template %s: no executor found\n", path)
gologger.Warning().Msgf("Could not parse workflow template %s: no executer found\n", path)
continue
}
workflow.Executers = append(workflow.Executers, &workflows.ProtocolExecuterPair{

View File

@ -1,44 +1,13 @@
package utils
import (
"reflect"
"strings"
)
func isEmpty(value interface{}) bool {
if value == nil {
return true
func IsBlank(value string) bool {
return strings.TrimSpace(value) == ""
}
reflectValue := reflect.ValueOf(value)
actualValueInterface := reflectValue.Interface()
// nolint:exhaustive //default branch handles other cases
switch reflect.TypeOf(value).Kind() {
case reflect.String:
reflectedValue := actualValueInterface.(string)
return strings.TrimSpace(reflectedValue) == ""
case reflect.Slice, reflect.Array, reflect.Map:
return reflectValue.Len() == 0
case reflect.Int32:
return IsEmpty(string(actualValueInterface.(rune)))
default:
if reflectValue.IsZero() {
return true
}
return false
}
}
func IsEmpty(value ...interface{}) bool {
for _, current := range value {
if IsNotEmpty(current) {
return false
}
}
return true
}
func IsNotEmpty(value interface{}) bool {
return !isEmpty(value)
func IsNotBlank(value string) bool {
return !IsBlank(value)
}

View File

@ -1,72 +0,0 @@
package utils
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
//nolint:scopelint //false-positive
func TestIsEmpty(t *testing.T) {
testCases := [...][2]interface{}{
{"", true},
{' ', true},
{'\t', true},
{'\n', true},
{" ", true},
{"\n", true},
{"\t", true},
{0, true},
{[]string{}, true},
{[0]string{}, true},
{[...]string{}, true},
{[]int{}, true},
{[0]int{}, true},
{[...]int{}, true},
{interface{}(nil), true},
{[]struct{}(nil), true},
{[]interface{}(nil), true},
{map[string]interface{}{}, true},
{nil, true},
{'a', false},
{1, false},
{3.14, false},
{" test ", false},
{[]string{"a"}, false},
{[...]string{"a"}, false},
{[2]string{"a", "b"}, false},
{[]int{1, 2}, false},
{[...]int{1, 2}, false},
{struct{ a string }{"a"}, false},
{&struct{ a string }{"a"}, false},
{[]struct{ a string }{{"b"}, {"b"}}, false},
{map[string]interface{}{"a": 13}, false},
}
for index, testCase := range testCases {
t.Run(fmt.Sprintf("%v # %d", testCase[0], index), func(t *testing.T) {
assert.Equal(t, testCase[1], IsEmpty(testCase[0]))
})
}
}
func TestVariadicIsEmpty(t *testing.T) {
testVariadicIsEmpty := func(expected bool, value ...interface{}) {
t.Run(fmt.Sprintf("%v", value), func(testCase *testing.T) {
assert.Equal(testCase, expected, IsEmpty(value...))
})
}
testVariadicIsEmpty(false, [2]int{1, 2}, [0]int{})
testVariadicIsEmpty(false, [0]int{}, [2]int{1, 2})
testVariadicIsEmpty(false, [0]int{}, " abc ")
testVariadicIsEmpty(false, [0]int{}, []string{}, 123)
testVariadicIsEmpty(false, [0]int{}, []string{}, []string{"a"})
testVariadicIsEmpty(false, [0]int{}, map[string]int{"a": 123}, map[string]interface{}{"b": "c"})
testVariadicIsEmpty(true, [0]int{}, "")
testVariadicIsEmpty(true, [0]int{}, []string{})
testVariadicIsEmpty(true, [0]int{}, []string{}, 0)
}