mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-17 21:25:27 +00:00
RES-84 # Improve Nuclei CLI interface (WIP)
* removed the generic isEmpty implementation
This commit is contained in:
parent
68a6d394e7
commit
2f162e859e
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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...)
|
||||
|
||||
@ -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{}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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("- ")
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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("- ")
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user