Files
trufflehog/pkg/common/filter.go
Kashif Khan de874d31bd Allow filesystem exclusion to eagerly prune the enumerated tree (#4008)
* Allow filesystem exclusion to eagerly prune the enumerated tree rather than skipping files afterwards

* added test case

* resolved charlie's comment

* resolved cody comments

* updated test case

* remove shouldExclude method

* added test cases and fixed the logic

* removed print statement

* renamed test case

* assert->require in tests
2025-04-14 10:44:08 +05:00

124 lines
3.1 KiB
Go

package common
import (
"bufio"
"fmt"
"os"
"regexp"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
type Filter struct {
include *FilterRuleSet
exclude *FilterRuleSet
}
type FilterRuleSet []regexp.Regexp
// FilterEmpty returns a Filter that always passes.
func FilterEmpty() *Filter {
filter, err := FilterFromFiles("", "")
if err != nil {
context.Background().Logger().Error(err, "could not create empty filter")
os.Exit(1)
}
return filter
}
// FilterFromFiles creates a Filter using the rules in the provided include and exclude files.
func FilterFromFiles(includeFilterPath, excludeFilterPath string) (*Filter, error) {
includeRules, err := FilterRulesFromFile(includeFilterPath)
if err != nil {
return nil, fmt.Errorf("could not create include rules: %s", err)
}
excludeRules, err := FilterRulesFromFile(excludeFilterPath)
if err != nil {
return nil, fmt.Errorf("could not create exclude rules: %s", err)
}
// If no includeFilterPath is provided, every pattern should pass the include rules.
if includeFilterPath == "" {
includeRules = &FilterRuleSet{*regexp.MustCompile("")}
}
filter := &Filter{
include: includeRules,
exclude: excludeRules,
}
return filter, nil
}
// FilterRulesFromFile loads the list of regular expression filter rules in `source` and creates a FilterRuleSet.
func FilterRulesFromFile(source string) (*FilterRuleSet, error) {
rules := FilterRuleSet{}
if source == "" {
return &rules, nil
}
commentPattern := regexp.MustCompile(`^\s*#`)
emptyLinePattern := regexp.MustCompile(`^\s*$`)
file, err := os.Open(source)
logger := context.Background().Logger().WithValues("file", source)
if err != nil {
logger.Error(err, "unable to open filter file", "file", source)
os.Exit(1)
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
logger.Error(err, "unable to close filter file")
os.Exit(1)
}
}(file)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if commentPattern.MatchString(line) {
continue
}
if emptyLinePattern.MatchString(line) {
continue
}
pattern, err := regexp.Compile(line)
if err != nil {
return nil, fmt.Errorf("can not compile regular expression: %s", line)
}
rules = append(rules, *pattern)
}
return &rules, nil
}
// Pass returns true if the include FilterRuleSet matches the pattern and the exclude FilterRuleSet does not match.
func (filter *Filter) Pass(object string) bool {
if filter == nil {
return true
}
excluded := filter.exclude.Matches(object)
included := filter.include.Matches(object)
return !excluded && included
}
// Matches will return true if any of the regular expressions in the FilterRuleSet match the pattern.
func (rules *FilterRuleSet) Matches(object string) bool {
if rules == nil {
return false
}
for _, rule := range *rules {
if rule.MatchString(object) {
return true
}
}
return false
}
// ShouldExclude return true if any regular expressions in the exclude FilterRuleSet matches the path.
func (filter *Filter) ShouldExclude(path string) bool {
return filter.exclude.Matches(path)
}