Path: blob/dev/pkg/protocols/file/request_test.go
2848 views
package file12import (3"archive/zip"4"bytes"5"compress/gzip"6"context"7"os"8"path/filepath"9"sync"10"sync/atomic"11"testing"12"time"1314"github.com/stretchr/testify/require"1516"github.com/projectdiscovery/nuclei/v3/pkg/model"17"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"18"github.com/projectdiscovery/nuclei/v3/pkg/operators"19"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors"20"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"21"github.com/projectdiscovery/nuclei/v3/pkg/output"22"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"23"github.com/projectdiscovery/nuclei/v3/pkg/testutils"24permissionutil "github.com/projectdiscovery/utils/permission"25)2627func zipFile(t *testing.T, fileName string, data []byte) []byte {28var b bytes.Buffer29w := zip.NewWriter(&b)30w1, err := w.Create(fileName)31require.NoError(t, err)32_, err = w1.Write(data)33require.NoError(t, err)34err = w.Close()35require.NoError(t, err)36return b.Bytes()37}3839func gzipFile(t *testing.T, data []byte) []byte {40var b bytes.Buffer41w := gzip.NewWriter(&b)42_, err := w.Write(data)43require.NoError(t, err)44err = w.Close()45require.NoError(t, err)46return b.Bytes()47}4849func TestFileExecuteWithResults(t *testing.T) {50var testCaseBase = []byte("TEST\r\n1.1.1.1\r\n")51const testCaseBaseFilename = "config.yaml"52var testCases = []struct {53fileName string54data []byte55}{56{57fileName: testCaseBaseFilename,58data: testCaseBase,59},60{61fileName: testCaseBaseFilename + ".gz",62data: gzipFile(t, testCaseBase),63},64{65fileName: "config.yaml.zip",66data: zipFile(t, testCaseBaseFilename, testCaseBase),67},68}6970for _, tt := range testCases {71options := testutils.DefaultOptions7273testutils.Init(options)74templateID := "testing-file"75executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{76ID: templateID,77Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},78})7980request := &Request{81ID: templateID,82MaxSize: "1Gb",83NoRecursive: false,84Extensions: []string{"all"},85DenyList: []string{".go"},86Archive: true,87Operators: operators.Operators{88Matchers: []*matchers.Matcher{{89Name: "test",90Part: "raw",91Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},92Words: []string{"1.1.1.1"},93}},94Extractors: []*extractors.Extractor{{95Part: "raw",96Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},97Regex: []string{"[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+"},98}},99},100options: executerOpts,101}102err := request.Compile(executerOpts)103require.Nil(t, err, "could not compile file request")104105tempDir, err := os.MkdirTemp("", "test-*")106require.Nil(t, err, "could not create temporary directory")107defer func() {108_ = os.RemoveAll(tempDir)109}()110111files := map[string][]byte{112tt.fileName: tt.data,113}114for k, v := range files {115err = os.WriteFile(filepath.Join(tempDir, k), v, permissionutil.TempFilePermission)116require.Nil(t, err, "could not write temporary file")117}118119var finalEvent *output.InternalWrappedEvent120t.Run("valid", func(t *testing.T) {121metadata := make(output.InternalEvent)122previous := make(output.InternalEvent)123ctxArgs := contextargs.NewWithInput(context.Background(), tempDir)124err := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {125finalEvent = event126})127require.Nil(t, err, "could not execute file request")128})129require.NotNil(t, finalEvent, "could not get event output from request")130require.Equal(t, 1, len(finalEvent.Results), "could not get correct number of results")131require.Equal(t, "test", finalEvent.Results[0].MatcherName, "could not get correct matcher name of results")132require.Equal(t, 1, len(finalEvent.Results[0].ExtractedResults), "could not get correct number of extracted results")133require.Equal(t, "1.1.1.1", finalEvent.Results[0].ExtractedResults[0], "could not get correct extracted results")134finalEvent = nil135}136}137138func TestFileProtocolConcurrentExecution(t *testing.T) {139tempDir, err := os.MkdirTemp("", "nuclei-test-*")140require.NoError(t, err)141142defer func() {143_ = os.RemoveAll(tempDir)144}()145146numFiles := 5147for i := range numFiles {148content := "TEST_CONTENT_MATCH_DATA"149filePath := filepath.Join(tempDir, "test_"+string(rune('0'+i))+".txt")150err := os.WriteFile(filePath, []byte(content), permissionutil.TempFilePermission)151require.NoError(t, err)152}153154options := testutils.DefaultOptions155testutils.Init(options)156templateID := "testing-file-concurrent"157executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{158ID: templateID,159Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},160})161162var timesMutex sync.Mutex163var processedFiles int64164165request := &Request{166ID: templateID,167MaxSize: "1Gb",168NoRecursive: false,169Extensions: []string{"txt"},170Archive: false,171Operators: operators.Operators{172Matchers: []*matchers.Matcher{{173Name: "test",174Part: "raw",175Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},176Words: []string{"TEST_CONTENT_MATCH_DATA"},177}},178},179options: executerOpts,180}181182err = request.Compile(executerOpts)183require.NoError(t, err)184185input := contextargs.NewWithInput(context.Background(), tempDir)186var results []*output.InternalWrappedEvent187var resultMutex sync.Mutex188189startTime := time.Now()190err = request.ExecuteWithResults(input, make(output.InternalEvent), make(output.InternalEvent), func(event *output.InternalWrappedEvent) {191atomic.AddInt64(&processedFiles, 1)192resultMutex.Lock()193results = append(results, event)194resultMutex.Unlock()195196// small delay to make timing differences more observable197time.Sleep(10 * time.Millisecond)198})199totalTime := time.Since(startTime)200require.NoError(t, err)201202finalProcessedFiles := atomic.LoadInt64(&processedFiles)203t.Logf("Total execution time: %v", totalTime)204t.Logf("Files processed: %d", finalProcessedFiles)205t.Logf("Results returned: %d", len(results))206207// test 1: all files should be processed208require.Equal(t, int64(numFiles), finalProcessedFiles, "Not all files were processed")209210// test 2: verify callback invocation timing shows concurrency211timesMutex.Lock()212defer timesMutex.Unlock()213}214215216