Skip to content

Commit e41f859

Browse files
Generalize CheckFileContent functions (#1670)
Co-authored-by: Azeem Shaikh <azeems@google.com>
1 parent 5656c3e commit e41f859

File tree

7 files changed

+224
-239
lines changed

7 files changed

+224
-239
lines changed

checks/dangerous_workflow.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,23 +98,37 @@ func DangerousWorkflow(c *checker.CheckRequest) checker.CheckResult {
9898
data := patternCbData{
9999
workflowPattern: make(map[dangerousResults]bool),
100100
}
101-
err := fileparser.CheckFilesContent(".github/workflows/*", false,
102-
c, validateGitHubActionWorkflowPatterns, &data)
101+
err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
102+
Pattern: ".github/workflows/*",
103+
CaseSensitive: false,
104+
},
105+
validateGitHubActionWorkflowPatterns, c.Dlogger, &data)
103106
return createResultForDangerousWorkflowPatterns(data, err)
104107
}
105108

106109
// Check file content.
107-
func validateGitHubActionWorkflowPatterns(path string, content []byte, dl checker.DetailLogger,
108-
data fileparser.FileCbData) (bool, error) {
110+
var validateGitHubActionWorkflowPatterns fileparser.DoWhileTrueOnFileContent = func(path string,
111+
content []byte,
112+
args ...interface{}) (bool, error) {
109113
if !fileparser.IsWorkflowFile(path) {
110114
return true, nil
111115
}
112116

117+
if len(args) != 2 {
118+
return false, fmt.Errorf(
119+
"validateGitHubActionWorkflowPatterns requires exactly 2 arguments: %w", errInvalidArgLength)
120+
}
121+
113122
// Verify the type of the data.
114-
pdata, ok := data.(*patternCbData)
123+
pdata, ok := args[1].(*patternCbData)
124+
if !ok {
125+
return false, fmt.Errorf(
126+
"validateGitHubActionWorkflowPatterns expects arg[0] of type *patternCbData: %w", errInvalidArgType)
127+
}
128+
dl, ok := args[0].(checker.DetailLogger)
115129
if !ok {
116-
// This never happens.
117-
panic("invalid type")
130+
return false, fmt.Errorf(
131+
"validateGitHubActionWorkflowPatterns expects arg[1] of type checker.DetailLogger: %w", errInvalidArgType)
118132
}
119133

120134
if !fileparser.CheckFileContainsCommands(content, "#") {

checks/fileparser/listing.go

Lines changed: 23 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ import (
2020
"path"
2121
"strings"
2222

23-
"github.com/ossf/scorecard/v4/checker"
2423
"github.com/ossf/scorecard/v4/clients"
2524
sce "github.com/ossf/scorecard/v4/errors"
2625
)
2726

2827
// isMatchingPath uses 'pattern' to shell-match the 'path' and its filename
2928
// 'caseSensitive' indicates the match should be case-sensitive. Default: no.
30-
func isMatchingPath(pattern, fullpath string, caseSensitive bool) (bool, error) {
31-
if !caseSensitive {
32-
pattern = strings.ToLower(pattern)
29+
func isMatchingPath(fullpath string, matchPathTo PathMatcher) (bool, error) {
30+
pattern := matchPathTo.Pattern
31+
if !matchPathTo.CaseSensitive {
32+
pattern = strings.ToLower(matchPathTo.Pattern)
3333
fullpath = strings.ToLower(fullpath)
3434
}
3535

@@ -55,87 +55,30 @@ func isTestdataFile(fullpath string) bool {
5555
strings.Contains(fullpath, "/testdata/")
5656
}
5757

58-
// FileCbData is any data the caller can act upon
59-
// to keep state.
60-
type FileCbData interface{}
61-
62-
// FileContentCb is the callback.
63-
// The bool returned indicates whether the CheckFilesContent2
64-
// should continue iterating over files or not.
65-
type FileContentCb func(path string, content []byte,
66-
dl checker.DetailLogger, data FileCbData) (bool, error)
67-
68-
// CheckFilesContent downloads the tar of the repository and calls the onFileContent() function
69-
// shellPathFnPattern is used for https://golang.org/pkg/path/#Match
70-
// Warning: the pattern is used to match (1) the entire path AND (2) the filename alone. This means:
71-
// - To scope the search to a directory, use "./dirname/*". Example, for the root directory,
72-
// use "./*".
73-
// - A pattern such as "*mypatern*" will match files containing mypattern in *any* directory.
74-
func CheckFilesContent(shellPathFnPattern string,
75-
caseSensitive bool,
76-
c *checker.CheckRequest,
77-
onFileContent FileContentCb,
78-
data FileCbData,
79-
) error {
80-
predicate := func(filepath string) (bool, error) {
81-
// Filter out test files.
82-
if isTestdataFile(filepath) {
83-
return false, nil
84-
}
85-
// Filter out files based on path/names using the pattern.
86-
b, err := isMatchingPath(shellPathFnPattern, filepath, caseSensitive)
87-
if err != nil {
88-
return false, err
89-
}
90-
return b, nil
91-
}
92-
93-
matchedFiles, err := c.RepoClient.ListFiles(predicate)
94-
if err != nil {
95-
// nolint: wrapcheck
96-
return err
97-
}
98-
99-
for _, file := range matchedFiles {
100-
content, err := c.RepoClient.GetFileContent(file)
101-
if err != nil {
102-
//nolint
103-
return err
104-
}
105-
106-
continueIter, err := onFileContent(file, content, c.Dlogger, data)
107-
if err != nil {
108-
return err
109-
}
110-
111-
if !continueIter {
112-
break
113-
}
114-
}
115-
116-
return nil
58+
// PathMatcher represents a query for a filepath.
59+
type PathMatcher struct {
60+
Pattern string
61+
CaseSensitive bool
11762
}
11863

119-
// FileContentCbV6 is the callback.
120-
// The bool returned indicates whether the CheckFilesContent2
121-
// should continue iterating over files or not.
122-
type FileContentCbV6 func(path string, content []byte, data FileCbData) (bool, error)
123-
124-
// CheckFilesContentV6 is the same as CheckFilesContent
125-
// but for use with separated check/policy code.
126-
func CheckFilesContentV6(shellPathFnPattern string,
127-
caseSensitive bool,
128-
repoClient clients.RepoClient,
129-
onFileContent FileContentCbV6,
130-
data FileCbData,
131-
) error {
64+
// DoWhileTrueOnFileContent takes a filepath, its content and
65+
// optional variadic args. It returns a boolean indicating whether
66+
// iterating over next files should continue.
67+
type DoWhileTrueOnFileContent func(path string, content []byte, args ...interface{}) (bool, error)
68+
69+
// OnMatchingFileContentDo matches all files listed by `repoClient` against `matchPathTo`
70+
// and on every successful match, runs onFileContent fn on the file's contents.
71+
// Continues iterating along the matched files until onFileContent returns
72+
// either a false value or an error.
73+
func OnMatchingFileContentDo(repoClient clients.RepoClient, matchPathTo PathMatcher,
74+
onFileContent DoWhileTrueOnFileContent, args ...interface{}) error {
13275
predicate := func(filepath string) (bool, error) {
13376
// Filter out test files.
13477
if isTestdataFile(filepath) {
13578
return false, nil
13679
}
13780
// Filter out files based on path/names using the pattern.
138-
b, err := isMatchingPath(shellPathFnPattern, filepath, caseSensitive)
81+
b, err := isMatchingPath(filepath, matchPathTo)
13982
if err != nil {
14083
return false, err
14184
}
@@ -144,18 +87,16 @@ func CheckFilesContentV6(shellPathFnPattern string,
14487

14588
matchedFiles, err := repoClient.ListFiles(predicate)
14689
if err != nil {
147-
// nolint: wrapcheck
148-
return err
90+
return fmt.Errorf("error during ListFiles: %w", err)
14991
}
15092

15193
for _, file := range matchedFiles {
15294
content, err := repoClient.GetFileContent(file)
15395
if err != nil {
154-
//nolint
155-
return err
96+
return fmt.Errorf("error during GetFileContent: %w", err)
15697
}
15798

158-
continueIter, err := onFileContent(file, content, data)
99+
continueIter, err := onFileContent(file, content, args...)
159100
if err != nil {
160101
return err
161102
}

checks/fileparser/listing_test.go

Lines changed: 12 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020

2121
"github.com/golang/mock/gomock"
2222

23-
"github.com/ossf/scorecard/v4/checker"
2423
mockrepo "github.com/ossf/scorecard/v4/clients/mockclients"
2524
)
2625

@@ -316,7 +315,10 @@ func Test_isMatchingPath(t *testing.T) {
316315
tt := tt // Re-initializing variable so it is not changed while executing the closure below
317316
t.Run(tt.name, func(t *testing.T) {
318317
t.Parallel()
319-
got, err := isMatchingPath(tt.args.pattern, tt.args.fullpath, tt.args.caseSensitive)
318+
got, err := isMatchingPath(tt.args.fullpath, PathMatcher{
319+
Pattern: tt.args.pattern,
320+
CaseSensitive: tt.args.caseSensitive,
321+
})
320322
if (err != nil) != tt.wantErr {
321323
t.Errorf("isMatchingPath() error = %v, wantErr %v", err, tt.wantErr)
322324
return
@@ -385,8 +387,8 @@ func Test_isTestdataFile(t *testing.T) {
385387
}
386388
}
387389

388-
// TestCheckFilesContentV6 tests the CheckFilesContentV6 function.
389-
func TestCheckFilesContentV6(t *testing.T) {
390+
// TestOnMatchingFileContentDo tests the OnMatchingFileContent function.
391+
func TestOnMatchingFileContent(t *testing.T) {
390392
t.Parallel()
391393
//nolint
392394
tests := []struct {
@@ -448,50 +450,6 @@ func TestCheckFilesContentV6(t *testing.T) {
448450
"Dockerfile.template.template.template.template",
449451
},
450452
},
451-
}
452-
453-
for _, tt := range tests {
454-
tt := tt // Re-initializing variable so it is not changed while executing the closure below
455-
t.Run(tt.name, func(t *testing.T) {
456-
t.Parallel()
457-
x := func(path string, content []byte, data FileCbData) (bool, error) {
458-
if tt.shouldFuncFail {
459-
//nolint
460-
return false, errors.New("test error")
461-
}
462-
if tt.shouldGetPredicateFail {
463-
return false, nil
464-
}
465-
return true, nil
466-
}
467-
468-
ctrl := gomock.NewController(t)
469-
mockRepo := mockrepo.NewMockRepoClient(ctrl)
470-
mockRepo.EXPECT().ListFiles(gomock.Any()).Return(tt.files, nil).AnyTimes()
471-
mockRepo.EXPECT().GetFileContent(gomock.Any()).Return(nil, nil).AnyTimes()
472-
473-
result := CheckFilesContentV6(tt.shellPattern, tt.caseSensitive, mockRepo, x, x)
474-
475-
if tt.wantErr && result == nil {
476-
t.Errorf("CheckFilesContentV6() = %v, want %v test name %v", result, tt.wantErr, tt.name)
477-
}
478-
})
479-
}
480-
}
481-
482-
// TestCheckFilesContent tests the CheckFilesContent function.
483-
func TestCheckFilesContent(t *testing.T) {
484-
t.Parallel()
485-
//nolint
486-
tests := []struct {
487-
name string
488-
wantErr bool
489-
shellPattern string
490-
caseSensitive bool
491-
shouldFuncFail bool
492-
shouldGetPredicateFail bool
493-
files []string
494-
}{
495453
{
496454
name: "no files",
497455
shellPattern: "Dockerfile",
@@ -548,8 +506,7 @@ func TestCheckFilesContent(t *testing.T) {
548506
tt := tt // Re-initializing variable so it is not changed while executing the closure below
549507
t.Run(tt.name, func(t *testing.T) {
550508
t.Parallel()
551-
x := func(path string, content []byte,
552-
dl checker.DetailLogger, data FileCbData) (bool, error) {
509+
x := func(path string, content []byte, args ...interface{}) (bool, error) {
553510
if tt.shouldFuncFail {
554511
//nolint
555512
return false, errors.New("test error")
@@ -565,14 +522,13 @@ func TestCheckFilesContent(t *testing.T) {
565522
mockRepo.EXPECT().ListFiles(gomock.Any()).Return(tt.files, nil).AnyTimes()
566523
mockRepo.EXPECT().GetFileContent(gomock.Any()).Return(nil, nil).AnyTimes()
567524

568-
c := checker.CheckRequest{
569-
RepoClient: mockRepo,
570-
}
571-
572-
result := CheckFilesContent(tt.shellPattern, tt.caseSensitive, &c, x, x)
525+
result := OnMatchingFileContentDo(mockRepo, PathMatcher{
526+
Pattern: tt.shellPattern,
527+
CaseSensitive: tt.caseSensitive,
528+
}, x)
573529

574530
if tt.wantErr && result == nil {
575-
t.Errorf("CheckFilesContentV6() = %v, want %v test name %v", result, tt.wantErr, tt.name)
531+
t.Errorf("OnMatchingFileContentDo() = %v, want %v test name %v", result, tt.wantErr, tt.name)
576532
}
577533
})
578534
}

checks/fuzzing.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ func init() {
3636

3737
func checkCFLite(c *checker.CheckRequest) (bool, error) {
3838
result := false
39-
e := fileparser.CheckFilesContent(".clusterfuzzlite/Dockerfile", true, c,
40-
func(path string, content []byte, dl checker.DetailLogger, data fileparser.FileCbData) (bool, error) {
41-
result = fileparser.CheckFileContainsCommands(content, "#")
42-
return false, nil
43-
}, nil)
39+
e := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
40+
Pattern: ".clusterfuzzlite/Dockerfile",
41+
CaseSensitive: true,
42+
}, func(path string, content []byte, args ...interface{}) (bool, error) {
43+
result = fileparser.CheckFileContainsCommands(content, "#")
44+
return false, nil
45+
}, nil)
4446
if e != nil {
4547
return result, fmt.Errorf("%w", e)
4648
}

0 commit comments

Comments
 (0)