diff --git a/v2/internal/runner/runner.go b/v2/internal/runner/runner.go index b85208753..a98147be1 100644 --- a/v2/internal/runner/runner.go +++ b/v2/internal/runner/runner.go @@ -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 diff --git a/v2/pkg/catalog/loader/filter/tag_filter.go b/v2/pkg/catalog/loader/filter/tag_filter.go index b27c47586..11a54f5d2 100644 --- a/v2/pkg/catalog/loader/filter/tag_filter.go +++ b/v2/pkg/catalog/loader/filter/tag_filter.go @@ -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 } diff --git a/v2/pkg/catalog/loader/loader.go b/v2/pkg/catalog/loader/loader.go index f996da1fd..0bab99c98 100644 --- a/v2/pkg/catalog/loader/loader.go +++ b/v2/pkg/catalog/loader/loader.go @@ -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...) diff --git a/v2/pkg/model/model.go b/v2/pkg/model/model.go index ffe62316a..3063da9dc 100644 --- a/v2/pkg/model/model.go +++ b/v2/pkg/model/model.go @@ -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{} diff --git a/v2/pkg/parsers/parser.go b/v2/pkg/parsers/parser.go index 72ac91e9d..9c0ef36a3 100644 --- a/v2/pkg/parsers/parser.go +++ b/v2/pkg/parsers/parser.go @@ -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 diff --git a/v2/pkg/reporting/exporters/sarif/sarif.go b/v2/pkg/reporting/exporters/sarif/sarif.go index 9579fde5a..a8c95b9ab 100644 --- a/v2/pkg/reporting/exporters/sarif/sarif.go +++ b/v2/pkg/reporting/exporters/sarif/sarif.go @@ -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 } diff --git a/v2/pkg/reporting/format/format.go b/v2/pkg/reporting/format/format.go index 942ac16b9..2e736d572 100644 --- a/v2/pkg/reporting/format/format.go +++ b/v2/pkg/reporting/format/format.go @@ -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("- ") diff --git a/v2/pkg/reporting/reporting.go b/v2/pkg/reporting/reporting.go index 3729a7c91..0f8b69b71 100644 --- a/v2/pkg/reporting/reporting.go +++ b/v2/pkg/reporting/reporting.go @@ -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 diff --git a/v2/pkg/reporting/trackers/jira/jira.go b/v2/pkg/reporting/trackers/jira/jira.go index 62dfddb25..a47fde7cc 100644 --- a/v2/pkg/reporting/trackers/jira/jira.go +++ b/v2/pkg/reporting/trackers/jira/jira.go @@ -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("- ") diff --git a/v2/pkg/templates/compile.go b/v2/pkg/templates/compile.go index 74d7a72ee..26139b703 100644 --- a/v2/pkg/templates/compile.go +++ b/v2/pkg/templates/compile.go @@ -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) diff --git a/v2/pkg/templates/workflows.go b/v2/pkg/templates/workflows.go index 6b94fe670..e9817e734 100644 --- a/v2/pkg/templates/workflows.go +++ b/v2/pkg/templates/workflows.go @@ -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{ diff --git a/v2/pkg/utils/utils.go b/v2/pkg/utils/utils.go index 3ffaf5cbd..8d6fc0cf7 100644 --- a/v2/pkg/utils/utils.go +++ b/v2/pkg/utils/utils.go @@ -1,44 +1,13 @@ package utils import ( - "reflect" "strings" ) -func isEmpty(value interface{}) bool { - if value == nil { - return true - } - - 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 IsBlank(value string) bool { + return strings.TrimSpace(value) == "" } -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) } diff --git a/v2/pkg/utils/utils_test.go b/v2/pkg/utils/utils_test.go deleted file mode 100644 index 43a0f9add..000000000 --- a/v2/pkg/utils/utils_test.go +++ /dev/null @@ -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) -}