Compare commits

...

10 Commits

Author SHA1 Message Date
Miccah Castorina c0752eae2c Update test 2023-07-26 00:36:42 -05:00
Miccah Castorina c6597969eb Add support for storing handler metadata
Currently it's a unstructured map[string]string that gets copied into
a Result.ExtraData after detection, but we might want to revisit this
decision when we get more handlers.
2023-07-26 00:24:11 -05:00
Richard Gomez f48a635c34 feat: update gitparse logic (#1486) 2023-07-25 17:52:34 -05:00
Miccah 91cbca941a Fix data race in context wrapper library (#1546) 2023-07-25 17:09:36 -05:00
Zachary Rice 1a1977f7e6 case insensitive (#1547) 2023-07-25 17:01:15 -05:00
Bill Rich f39303495a Add commitsScanned metrics (#1533)
* Add commitsScanned metrics

* Just keep commit count
2023-07-25 11:31:01 -07:00
ahrav b5b01d3eba [chore] - optimize chunker (#1535)
* Use chunkbytes that includes the size of peek.

* linter.

* continue.

* add TotalChunkSize const.
2023-07-24 19:30:29 -07:00
Zachary Rice 85f363f093 init (#1538) 2023-07-24 19:09:57 -05:00
Dustin Decker 302c3ce8d1 Downgrade couchbase dep (#1543)
* update couchbase dep

* downgrade couchbase dep
2023-07-24 17:00:13 -07:00
Dustin Decker fd6c281589 update couchbase dep (#1540) 2023-07-24 16:27:11 -07:00
16 changed files with 2232 additions and 394 deletions
+2 -2
View File
@@ -8,6 +8,7 @@ require (
cloud.google.com/go/secretmanager v1.11.1 cloud.google.com/go/secretmanager v1.11.1
cloud.google.com/go/storage v1.31.0 cloud.google.com/go/storage v1.31.0
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
github.com/BobuSumisu/aho-corasick v1.0.3
github.com/TheZeroSlave/zapsentry v1.17.0 github.com/TheZeroSlave/zapsentry v1.17.0
github.com/aws/aws-sdk-go v1.44.83 github.com/aws/aws-sdk-go v1.44.83
github.com/bill-rich/disk-buffer-reader v0.1.7 github.com/bill-rich/disk-buffer-reader v0.1.7
@@ -44,7 +45,6 @@ require (
github.com/mholt/archiver/v4 v4.0.0-alpha.8 github.com/mholt/archiver/v4 v4.0.0-alpha.8
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/paulbellamy/ratecounter v0.2.0 github.com/paulbellamy/ratecounter v0.2.0
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.16.0 github.com/prometheus/client_golang v1.16.0
github.com/rabbitmq/amqp091-go v1.8.1 github.com/rabbitmq/amqp091-go v1.8.1
@@ -94,7 +94,7 @@ require (
github.com/cloudflare/circl v1.3.3 // indirect github.com/cloudflare/circl v1.3.3 // indirect
github.com/connesc/cipherio v0.2.1 // indirect github.com/connesc/cipherio v0.2.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/couchbase/gocbcore/v10 v10.2.3 // indirect github.com/couchbase/gocbcore/v10 v10.2.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/docker/cli v23.0.5+incompatible // indirect github.com/docker/cli v23.0.5+incompatible // indirect
+4 -3
View File
@@ -51,6 +51,8 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BobuSumisu/aho-corasick v1.0.3 h1:uuf+JHwU9CHP2Vx+wAy6jcksJThhJS9ehR8a+4nPE9g=
github.com/BobuSumisu/aho-corasick v1.0.3/go.mod h1:hm4jLcvZKI2vRF2WDU1N4p/jpWtpOzp3nLmi9AzX/XE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -119,8 +121,9 @@ github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSk
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
github.com/couchbase/gocb/v2 v2.6.3 h1:5RsMo+RRfK0mVxHLAfpBz3/tHlgXZb1WBNItLk9Ab+c= github.com/couchbase/gocb/v2 v2.6.3 h1:5RsMo+RRfK0mVxHLAfpBz3/tHlgXZb1WBNItLk9Ab+c=
github.com/couchbase/gocb/v2 v2.6.3/go.mod h1:yF5F6BHTZ/ZowhEuZbySbXrlI4rHd1TIhm5azOaMbJU= github.com/couchbase/gocb/v2 v2.6.3/go.mod h1:yF5F6BHTZ/ZowhEuZbySbXrlI4rHd1TIhm5azOaMbJU=
github.com/couchbase/gocbcore/v10 v10.2.3 h1:PEkRSNSkKjUBXx82Ucr094+anoiCG5GleOOQZOHo6D4=
github.com/couchbase/gocbcore/v10 v10.2.3/go.mod h1:lYQIIk+tzoMcwtwU5GzPbDdqEkwkH3isI2rkSpfL0oM= github.com/couchbase/gocbcore/v10 v10.2.3/go.mod h1:lYQIIk+tzoMcwtwU5GzPbDdqEkwkH3isI2rkSpfL0oM=
github.com/couchbase/gocbcore/v10 v10.2.4 h1:TDTQ1mSBUw9eajuV71ZDJcLRbmfGcsvZV9SMeUDAVGc=
github.com/couchbase/gocbcore/v10 v10.2.4/go.mod h1:lYQIIk+tzoMcwtwU5GzPbDdqEkwkH3isI2rkSpfL0oM=
github.com/couchbaselabs/gocaves/client v0.0.0-20230307083111-cc3960c624b1/go.mod h1:AVekAZwIY2stsJOMWLAS/0uA/+qdp7pjO8EHnl61QkY= github.com/couchbaselabs/gocaves/client v0.0.0-20230307083111-cc3960c624b1/go.mod h1:AVekAZwIY2stsJOMWLAS/0uA/+qdp7pjO8EHnl61QkY=
github.com/couchbaselabs/gocaves/client v0.0.0-20230404095311-05e3ba4f0259 h1:2TXy68EGEzIMHOx9UvczR5ApVecwCfQZ0LjkmwMI6g4= github.com/couchbaselabs/gocaves/client v0.0.0-20230404095311-05e3ba4f0259 h1:2TXy68EGEzIMHOx9UvczR5ApVecwCfQZ0LjkmwMI6g4=
github.com/couchbaselabs/gocaves/client v0.0.0-20230404095311-05e3ba4f0259/go.mod h1:AVekAZwIY2stsJOMWLAS/0uA/+qdp7pjO8EHnl61QkY= github.com/couchbaselabs/gocaves/client v0.0.0-20230404095311-05e3ba4f0259/go.mod h1:AVekAZwIY2stsJOMWLAS/0uA/+qdp7pjO8EHnl61QkY=
@@ -382,8 +385,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/paulbellamy/ratecounter v0.2.0 h1:2L/RhJq+HA8gBQImDXtLPrDXK5qAj6ozWVK/zFXVJGs= github.com/paulbellamy/ratecounter v0.2.0 h1:2L/RhJq+HA8gBQImDXtLPrDXK5qAj6ozWVK/zFXVJGs=
github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE=
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 h1:lL+y4Xv20pVlCGyLzNHRC0I0rIHhIL1lTvHizoS/dU8=
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw=
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
+7 -2
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"runtime/debug" "runtime/debug"
"sync"
"time" "time"
"github.com/go-logr/logr" "github.com/go-logr/logr"
@@ -28,8 +29,9 @@ type CancelFunc = context.CancelFunc
type logCtx struct { type logCtx struct {
// Embed context.Context to get all methods for free. // Embed context.Context to get all methods for free.
context.Context context.Context
log logr.Logger log logr.Logger
err *error err *error
errLock *sync.Mutex
} }
// Logger returns a structured logger. // Logger returns a structured logger.
@@ -145,8 +147,11 @@ func captureCancelCallstack(ctx logCtx, f context.CancelFunc) (Context, context.
if ctx.err == nil { if ctx.err == nil {
var err error var err error
ctx.err = &err ctx.err = &err
ctx.errLock = &sync.Mutex{}
} }
return ctx, func() { return ctx, func() {
ctx.errLock.Lock()
defer ctx.errLock.Unlock()
// We must check Err() before calling f() since f() sets the error. // We must check Err() before calling f() since f() sets the error.
// If there's already an error, do nothing special. // If there's already an error, do nothing special.
if ctx.Err() != nil { if ctx.Err() != nil {
+6
View File
@@ -212,3 +212,9 @@ func TestErrCallstackTimeoutCancel(t *testing.T) {
cancel() cancel()
assert.Equal(t, err, ctx.Err()) assert.Equal(t, err, ctx.Err())
} }
func TestRace(t *testing.T) {
_, cancel := WithCancel(Background())
go cancel()
cancel()
}
-1
View File
@@ -36,7 +36,6 @@ func (s Scanner) Keywords() []string {
// FromData will find and optionally verify NpmToken secrets in a given set of bytes. // FromData will find and optionally verify NpmToken secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data) dataStr := string(data)
matches := keyPat.FindAllStringSubmatch(dataStr, -1) matches := keyPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range matches { for _, match := range matches {
if len(match) != 2 { if len(match) != 2 {
+19 -14
View File
@@ -10,7 +10,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
ahocorasick "github.com/petar-dambovaliev/aho-corasick" ahocorasick "github.com/BobuSumisu/aho-corasick"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
@@ -43,7 +43,7 @@ type Engine struct {
// prefilter is a ahocorasick struct used for doing efficient string // prefilter is a ahocorasick struct used for doing efficient string
// matching given a set of words (keywords from the rules in the config) // matching given a set of words (keywords from the rules in the config)
prefilter ahocorasick.AhoCorasick prefilter ahocorasick.Trie
} }
type EngineOption func(*Engine) type EngineOption func(*Engine)
@@ -145,18 +145,16 @@ func Start(ctx context.Context, options ...EngineOption) *Engine {
// on keywords // on keywords
keywords := []string{} keywords := []string{}
for _, d := range e.detectors[false] { for _, d := range e.detectors[false] {
keywords = append(keywords, d.Keywords()...) for _, kw := range d.Keywords() {
keywords = append(keywords, strings.ToLower(kw))
}
} }
for _, d := range e.detectors[true] { for _, d := range e.detectors[true] {
keywords = append(keywords, d.Keywords()...) for _, kw := range d.Keywords() {
keywords = append(keywords, strings.ToLower(kw))
}
} }
builder := ahocorasick.NewAhoCorasickBuilder(ahocorasick.Opts{ e.prefilter = *ahocorasick.NewTrieBuilder().AddStrings(keywords).Build()
AsciiCaseInsensitive: true,
MatchOnlyWholeWords: false,
MatchKind: ahocorasick.LeftMostLongestMatch,
DFA: true,
})
e.prefilter = builder.Build(keywords)
ctx.Logger().Info("loaded decoders", "count", len(e.decoders)) ctx.Logger().Info("loaded decoders", "count", len(e.decoders))
ctx.Logger().Info("loaded detectors", ctx.Logger().Info("loaded detectors",
@@ -297,8 +295,8 @@ func (e *Engine) detectorWorker(ctx context.Context) {
} }
// build a map of all keywords that were matched in the chunk // build a map of all keywords that were matched in the chunk
for _, m := range e.prefilter.FindAll(string(decoded.Data)) { for _, m := range e.prefilter.MatchString(strings.ToLower(string(decoded.Data))) {
matchedKeywords[strings.ToLower(string(decoded.Data[m.Start():m.End()]))] = struct{}{} matchedKeywords[strings.ToLower(m.MatchString())] = struct{}{}
} }
for verify, detectorsSet := range e.detectors { for verify, detectorsSet := range e.detectors {
@@ -351,8 +349,15 @@ func (e *Engine) detectorWorker(ctx context.Context) {
continue continue
} }
result.DecoderType = decoderType result.DecoderType = decoderType
if result.ExtraData == nil && chunk.HandleMetadata != nil {
result.ExtraData = chunk.HandleMetadata
} else {
for k, v := range chunk.HandleMetadata {
// TODO: Check key collisions.
result.ExtraData[k] = v
}
}
chunkResults = append(chunkResults, detectors.CopyMetadata(resultChunk, result)) chunkResults = append(chunkResults, detectors.CopyMetadata(resultChunk, result))
} }
if len(results) > 0 { if len(results) > 0 {
elapsed := time.Since(start) elapsed := time.Since(start)
+354 -113
View File
@@ -51,6 +51,47 @@ type Parser struct {
dateFormat string dateFormat string
} }
type ParseState int
const (
Initial ParseState = iota
CommitLine
AuthorLine
DateLine
MessageStartLine
MessageLine
MessageEndLine
DiffLine
ModeLine
IndexLine
FromFileLine
ToFileLine
BinaryFileLine
HunkLineNumberLine
HunkContentLine
ParseFailure
)
func (state ParseState) String() string {
return [...]string{
"Initial",
"CommitLine",
"AuthorLine",
"DateLine",
"MessageStartLine",
"MessageLine",
"MessageEndLine",
"DiffLine",
"ModeLine",
"IndexLine",
"FromFileLine",
"ToFileLine",
"BinaryFileLine",
"HunkLineNumberLine",
"HunkContentLine",
}[state]
}
// WithMaxDiffSize sets maxDiffSize option. Diffs larger than maxDiffSize will // WithMaxDiffSize sets maxDiffSize option. Diffs larger than maxDiffSize will
// be truncated. // be truncated.
func WithMaxDiffSize(maxDiffSize int) Option { func WithMaxDiffSize(maxDiffSize int) Option {
@@ -137,7 +178,7 @@ func (c *Parser) RepoPath(ctx context.Context, source string, head string, abbre
cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_DIR=%s", filepath.Join(absPath, ".git"))) cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_DIR=%s", filepath.Join(absPath, ".git")))
} }
return c.executeCommand(ctx, cmd) return c.executeCommand(ctx, cmd, false)
} }
// Staged parses the output of the `git diff` command for the `source` path. // Staged parses the output of the `git diff` command for the `source` path.
@@ -152,11 +193,11 @@ func (c *Parser) Staged(ctx context.Context, source string) (chan Commit, error)
cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_DIR=%s", filepath.Join(absPath, ".git"))) cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_DIR=%s", filepath.Join(absPath, ".git")))
} }
return c.executeCommand(ctx, cmd) return c.executeCommand(ctx, cmd, true)
} }
// executeCommand runs an exec.Cmd, reads stdout and stderr, and waits for the Cmd to complete. // executeCommand runs an exec.Cmd, reads stdout and stderr, and waits for the Cmd to complete.
func (c *Parser) executeCommand(ctx context.Context, cmd *exec.Cmd) (chan Commit, error) { func (c *Parser) executeCommand(ctx context.Context, cmd *exec.Cmd, isStaged bool) (chan Commit, error) {
commitChan := make(chan Commit, 64) commitChan := make(chan Commit, 64)
stdOut, err := cmd.StdoutPipe() stdOut, err := cmd.StdoutPipe()
@@ -181,7 +222,7 @@ func (c *Parser) executeCommand(ctx context.Context, cmd *exec.Cmd) (chan Commit
}() }()
go func() { go func() {
c.FromReader(ctx, stdOut, commitChan) c.FromReader(ctx, stdOut, commitChan, isStaged)
if err := cmd.Wait(); err != nil { if err := cmd.Wait(); err != nil {
ctx.Logger().V(2).Info("Error waiting for git command to complete.", "error", err) ctx.Logger().V(2).Info("Error waiting for git command to complete.", "error", err)
} }
@@ -190,14 +231,15 @@ func (c *Parser) executeCommand(ctx context.Context, cmd *exec.Cmd) (chan Commit
return commitChan, nil return commitChan, nil
} }
func (c *Parser) FromReader(ctx context.Context, stdOut io.Reader, commitChan chan Commit) { func (c *Parser) FromReader(ctx context.Context, stdOut io.Reader, commitChan chan Commit, isStaged bool) {
outReader := bufio.NewReader(stdOut) outReader := bufio.NewReader(stdOut)
var ( var (
currentCommit *Commit currentCommit *Commit
currentDiff *Diff currentDiff *Diff
recentlyPassedAuthor bool
totalLogSize int totalLogSize int
) )
var latestState = Initial
defer common.RecoverWithExit(ctx) defer common.RecoverWithExit(ctx)
defer close(commitChan) defer close(commitChan)
@@ -205,12 +247,16 @@ func (c *Parser) FromReader(ctx context.Context, stdOut io.Reader, commitChan ch
if common.IsDone(ctx) { if common.IsDone(ctx) {
break break
} }
line, err := outReader.ReadBytes([]byte("\n")[0]) line, err := outReader.ReadBytes([]byte("\n")[0])
if err != nil && len(line) == 0 { if err != nil && len(line) == 0 {
break break
} }
switch { switch {
case isCommitLine(line): case isCommitLine(isStaged, latestState, line):
latestState = CommitLine
// If there is a currentDiff, add it to currentCommit. // If there is a currentDiff, add it to currentCommit.
if currentDiff != nil && currentDiff.Content.Len() > 0 { if currentDiff != nil && currentDiff.Content.Len() > 0 {
currentCommit.Diffs = append(currentCommit.Diffs, *currentDiff) currentCommit.Diffs = append(currentCommit.Diffs, *currentDiff)
@@ -230,16 +276,31 @@ func (c *Parser) FromReader(ctx context.Context, stdOut io.Reader, commitChan ch
if len(line) >= 47 { if len(line) >= 47 {
currentCommit.Hash = string(line[7:47]) currentCommit.Hash = string(line[7:47])
} }
case isAuthorLine(line): case isAuthorLine(isStaged, latestState, line):
latestState = AuthorLine
currentCommit.Author = strings.TrimRight(string(line[8:]), "\n") currentCommit.Author = strings.TrimRight(string(line[8:]), "\n")
recentlyPassedAuthor = true case isDateLine(isStaged, latestState, line):
case isDateLine(line): latestState = DateLine
date, err := time.Parse(c.dateFormat, strings.TrimSpace(string(line[6:]))) date, err := time.Parse(c.dateFormat, strings.TrimSpace(string(line[6:])))
if err != nil { if err != nil {
ctx.Logger().V(2).Info("Could not parse date from git stream.", "error", err) ctx.Logger().V(2).Info("Could not parse date from git stream.", "error", err)
} }
currentCommit.Date = date currentCommit.Date = date
case isDiffLine(line): case isMessageStartLine(isStaged, latestState, line):
latestState = MessageStartLine
// NoOp
case isMessageLine(isStaged, latestState, line):
latestState = MessageLine
currentCommit.Message.Write(line[4:])
case isMessageEndLine(isStaged, latestState, line):
latestState = MessageEndLine
// NoOp
case isDiffLine(isStaged, latestState, line):
latestState = DiffLine
// This should never be nil, but check in case the stdin stream is messed up. // This should never be nil, but check in case the stdin stream is messed up.
if currentCommit == nil { if currentCommit == nil {
currentCommit = &Commit{} currentCommit = &Commit{}
@@ -267,27 +328,29 @@ func (c *Parser) FromReader(ctx context.Context, stdOut io.Reader, commitChan ch
} }
} }
currentDiff = &Diff{} currentDiff = &Diff{}
case isModeLine(line): case isModeLine(isStaged, latestState, line):
latestState = ModeLine
// NoOp // NoOp
case isIndexLine(line): case isIndexLine(isStaged, latestState, line):
recentlyPassedAuthor = false latestState = IndexLine
// NoOp // NoOp
case isPlusFileLine(line): case isBinaryLine(isStaged, latestState, line):
currentDiff.PathB = strings.TrimRight(strings.TrimRight(string(line[6:]), "\n"), "\t") // Trim the newline and tab characters. https://github.com/trufflesecurity/trufflehog/issues/1060 latestState = BinaryFileLine
case isMinusFileLine(line):
// NoOp
case isPlusDiffLine(line):
currentDiff.Content.Write(line[1:])
case isMinusDiffLine(line):
// NoOp. We only care about additions.
case (isMessageLine(line) && recentlyPassedAuthor):
currentCommit.Message.Write(line[4:])
case isContextDiffLine(line):
currentDiff.Content.Write([]byte("\n"))
case isBinaryLine(line):
currentDiff.IsBinary = true currentDiff.IsBinary = true
currentDiff.PathB = pathFromBinaryLine(line) currentDiff.PathB = pathFromBinaryLine(line)
case isLineNumberDiffLine(line): case isFromFileLine(isStaged, latestState, line):
latestState = FromFileLine
// NoOp
case isToFileLine(isStaged, latestState, line):
latestState = ToFileLine
// TODO: Is this fix still required?
currentDiff.PathB = strings.TrimRight(strings.TrimRight(string(line[6:]), "\n"), "\t") // Trim the newline and tab characters. https://github.com/trufflesecurity/trufflehog/issues/1060
case isHunkLineNumberLine(isStaged, latestState, line):
latestState = HunkLineNumberLine
// TODO: Is it still necessary to check whether the currentDiff is nil?
if currentDiff != nil && currentDiff.Content.Len() > 0 { if currentDiff != nil && currentDiff.Content.Len() > 0 {
currentCommit.Diffs = append(currentCommit.Diffs, *currentDiff) currentCommit.Diffs = append(currentCommit.Diffs, *currentDiff)
} }
@@ -305,7 +368,54 @@ func (c *Parser) FromReader(ctx context.Context, stdOut io.Reader, commitChan ch
currentDiff.LineStart = lineStart currentDiff.LineStart = lineStart
} }
} }
case isHunkContextLine(isStaged, latestState, line):
if latestState != HunkContentLine {
latestState = HunkContentLine
}
// TODO: Why do we care about this? It creates empty lines in the diff. If there are no plusLines, it's just newlines.
currentDiff.Content.Write([]byte("\n"))
case isHunkPlusLine(isStaged, latestState, line):
if latestState != HunkContentLine {
latestState = HunkContentLine
}
currentDiff.Content.Write(line[1:])
case isHunkMinusLine(isStaged, latestState, line):
if latestState != HunkContentLine {
latestState = HunkContentLine
}
// NoOp. We only care about additions.
case isHunkNewlineWarningLine(isStaged, latestState, line):
if latestState != HunkContentLine {
latestState = HunkContentLine
}
// NoOp
case isHunkEmptyLine(isStaged, latestState, line):
if latestState != HunkContentLine {
latestState = HunkContentLine
}
// NoOp
case isCommitSeparatorLine(isStaged, latestState, line):
// NoOp
default:
// Skip ahead until we find the next diff or commit.
if latestState == ParseFailure {
continue
}
// Here be dragons...
// Build an informative error message.
var err error
if currentCommit != nil && currentCommit.Hash != "" {
err = fmt.Errorf(`failed to parse line "%s" after state "%s" (commit=%s)`, line, latestState, currentCommit.Hash)
} else {
err = fmt.Errorf(`failed to parse line "%s" after state "%s"`, line, latestState)
}
ctx.Logger().V(2).Error(err, "Recovering at the latest commit or diff...\n")
latestState = ParseFailure
} }
if currentDiff.Content.Len() > c.maxDiffSize { if currentDiff.Content.Len() > c.maxDiffSize {
ctx.Logger().V(2).Info(fmt.Sprintf( ctx.Logger().V(2).Info(fmt.Sprintf(
"Diff for %s exceeded MaxDiffSize(%d)", currentDiff.PathB, c.maxDiffSize, "Diff for %s exceeded MaxDiffSize(%d)", currentDiff.PathB, c.maxDiffSize,
@@ -318,125 +428,142 @@ func (c *Parser) FromReader(ctx context.Context, stdOut io.Reader, commitChan ch
ctx.Logger().V(2).Info("finished parsing git log.", "total_log_size", totalLogSize) ctx.Logger().V(2).Info("finished parsing git log.", "total_log_size", totalLogSize)
} }
func cleanupParse(currentCommit *Commit, currentDiff *Diff, commitChan chan Commit, totalLogSize *int) { // commit 7a95bbf0199e280a0e42dbb1d1a3f56cdd0f6e05
if currentDiff != nil && currentDiff.Content.Len() > 0 { func isCommitLine(isStaged bool, latestState ParseState, line []byte) bool {
currentCommit.Diffs = append(currentCommit.Diffs, *currentDiff) if isStaged || !(latestState == Initial ||
latestState == MessageStartLine ||
latestState == MessageEndLine ||
latestState == ModeLine ||
latestState == IndexLine ||
latestState == BinaryFileLine ||
latestState == ToFileLine ||
latestState == HunkContentLine ||
latestState == ParseFailure) {
return false
} }
if currentCommit != nil {
commitChan <- *currentCommit
if totalLogSize != nil {
*totalLogSize += currentCommit.Size
}
}
}
// Date: Tue Aug 10 15:20:40 2021 +0100 if len(line) > 7 && bytes.Equal(line[:7], []byte("commit ")) {
func isDateLine(line []byte) bool {
if len(line) > 7 && bytes.Equal(line[:5], []byte("Date:")) {
return true return true
} }
return false return false
} }
// Author: Bill Rich <bill.rich@trufflesec.com> // Author: Bill Rich <bill.rich@trufflesec.com>
func isAuthorLine(line []byte) bool { func isAuthorLine(isStaged bool, latestState ParseState, line []byte) bool {
if isStaged || !(latestState == CommitLine) {
return false
}
if len(line) > 8 && bytes.Equal(line[:7], []byte("Author:")) { if len(line) > 8 && bytes.Equal(line[:7], []byte("Author:")) {
return true return true
} }
return false return false
} }
// commit 7a95bbf0199e280a0e42dbb1d1a3f56cdd0f6e05 // Date: Tue Aug 10 15:20:40 2021 +0100
func isCommitLine(line []byte) bool { func isDateLine(isStaged bool, latestState ParseState, line []byte) bool {
if len(line) > 7 && bytes.Equal(line[:6], []byte("commit")) { if isStaged || latestState != AuthorLine {
return false
}
if len(line) > 7 && bytes.Equal(line[:5], []byte("Date:")) {
return true return true
} }
return false return false
} }
// diff --git a/internal/addrs/move_endpoint_module.go b/internal/addrs/move_endpoint_module.go // Line directly after Date with only a newline.
func isDiffLine(line []byte) bool { func isMessageStartLine(isStaged bool, latestState ParseState, line []byte) bool {
if len(line) > 5 && bytes.Equal(line[:4], []byte("diff")) { if isStaged || latestState != DateLine {
return true return false
} }
return false // TODO: Improve the implementation of this and isMessageEndLine
} if len(strings.TrimRight(string(line[:]), "\r\n")) == 0 {
// index 1ed6fbee1..aea1e643a 100644
func isIndexLine(line []byte) bool {
if len(line) > 6 && bytes.Equal(line[:5], []byte("index")) {
return true
}
return false
}
// new file mode 100644
func isModeLine(line []byte) bool {
if len(line) > 13 && bytes.Equal(line[:13], []byte("new file mode")) {
return true
}
return false
}
// --- a/internal/addrs/move_endpoint_module.go
func isMinusFileLine(line []byte) bool {
if len(line) >= 6 && bytes.Equal(line[:3], []byte("---")) {
return true
}
return false
}
// +++ b/internal/addrs/move_endpoint_module.go
func isPlusFileLine(line []byte) bool {
if len(line) >= 6 && bytes.Equal(line[:4], []byte("+++ ")) {
return true
}
return false
}
// +fmt.Println("ok")
func isPlusDiffLine(line []byte) bool {
if len(line) >= 1 && bytes.Equal(line[:1], []byte("+")) {
return true
}
return false
}
// -fmt.Println("ok")
func isMinusDiffLine(line []byte) bool {
if len(line) >= 1 && bytes.Equal(line[:1], []byte("-")) {
return true
}
return false
}
// fmt.Println("ok")
func isContextDiffLine(line []byte) bool {
if len(line) >= 1 && bytes.Equal(line[:1], []byte(" ")) {
return true return true
} }
return false return false
} }
// Line that starts with 4 spaces // Line that starts with 4 spaces
func isMessageLine(line []byte) bool { func isMessageLine(isStaged bool, latestState ParseState, line []byte) bool {
if isStaged || !(latestState == MessageStartLine || latestState == MessageLine) {
return false
}
if len(line) > 4 && bytes.Equal(line[:4], []byte(" ")) { if len(line) > 4 && bytes.Equal(line[:4], []byte(" ")) {
return true return true
} }
return false return false
} }
// Binary files /dev/null and b/plugin.sig differ // Line directly after MessageLine with only a newline.
func isBinaryLine(line []byte) bool { func isMessageEndLine(isStaged bool, latestState ParseState, line []byte) bool {
if len(line) > 7 && bytes.Equal(line[:6], []byte("Binary")) { if isStaged || latestState != MessageLine {
return false
}
if len(strings.TrimRight(string(line[:]), "\r\n")) == 0 {
return true return true
} }
return false return false
} }
// @@ -298 +298 @@ func maxRetryErrorHandler(resp *http.Response, err error, numTries int) // diff --git a/internal/addrs/move_endpoint_module.go b/internal/addrs/move_endpoint_module.go
func isLineNumberDiffLine(line []byte) bool { func isDiffLine(isStaged bool, latestState ParseState, line []byte) bool {
if len(line) >= 8 && bytes.Equal(line[:2], []byte("@@")) { if !(latestState == MessageStartLine || // Empty commit messages can go from MessageStart->Diff
latestState == MessageEndLine ||
latestState == BinaryFileLine ||
latestState == IndexLine ||
latestState == HunkContentLine ||
latestState == ParseFailure) {
if latestState == Initial && !isStaged {
return false
}
}
if len(line) > 11 && bytes.Equal(line[:11], []byte("diff --git ")) {
return true
}
return false
}
// old mode 100644
// new mode 100755
// new file mode 100644
// similarity index 100%
// rename from old.txt
// rename to new.txt
// deleted file mode 100644
func isModeLine(isStaged bool, latestState ParseState, line []byte) bool {
if !(latestState == DiffLine || latestState == ModeLine) {
return false
}
// This could probably be better written.
if (len(line) > 17 && bytes.Equal(line[:17], []byte("deleted file mode"))) ||
(len(line) > 16 && bytes.Equal(line[:16], []byte("similarity index"))) ||
(len(line) > 13 && bytes.Equal(line[:13], []byte("new file mode"))) ||
(len(line) > 11 && bytes.Equal(line[:11], []byte("rename from"))) ||
(len(line) > 9 && bytes.Equal(line[:9], []byte("rename to"))) ||
(len(line) > 8 && bytes.Equal(line[:8], []byte("old mode"))) ||
(len(line) > 8 && bytes.Equal(line[:8], []byte("new mode"))) {
return true
}
return false
}
// index 1ed6fbee1..aea1e643a 100644
// index 00000000..e69de29b
func isIndexLine(isStaged bool, latestState ParseState, line []byte) bool {
if !(latestState == DiffLine || latestState == ModeLine) {
return false
}
if len(line) > 6 && bytes.Equal(line[:6], []byte("index ")) {
return true
}
return false
}
// Binary files /dev/null and b/plugin.sig differ
func isBinaryLine(isStaged bool, latestState ParseState, line []byte) bool {
if latestState != IndexLine {
return false
}
if len(line) > 7 && bytes.Equal(line[:6], []byte("Binary")) {
return true return true
} }
return false return false
@@ -453,3 +580,117 @@ func pathFromBinaryLine(line []byte) string {
bRaw := sbytes[1] bRaw := sbytes[1]
return string(bRaw[:len(bRaw)-7]) // drop the "b/" and " differ" return string(bRaw[:len(bRaw)-7]) // drop the "b/" and " differ"
} }
// --- a/internal/addrs/move_endpoint_module.go
func isFromFileLine(isStaged bool, latestState ParseState, line []byte) bool {
if latestState != IndexLine {
return false
}
if len(line) >= 6 && bytes.Equal(line[:4], []byte("--- ")) {
return true
}
return false
}
// +++ b/internal/addrs/move_endpoint_module.go
func isToFileLine(isStaged bool, latestState ParseState, line []byte) bool {
if latestState != FromFileLine {
return false
}
if len(line) >= 6 && bytes.Equal(line[:4], []byte("+++ ")) {
return true
}
return false
}
// @@ -298 +298 @@ func maxRetryErrorHandler(resp *http.Response, err error, numTries int)
func isHunkLineNumberLine(isStaged bool, latestState ParseState, line []byte) bool {
if !(latestState == ToFileLine || latestState == HunkContentLine) {
return false
}
if len(line) >= 8 && bytes.Equal(line[:2], []byte("@@")) {
return true
}
return false
}
// fmt.Println("ok")
// (There's a space before `fmt` that gets removed by the formatter.)
func isHunkContextLine(isStaged bool, latestState ParseState, line []byte) bool {
if !(latestState == HunkLineNumberLine || latestState == HunkContentLine) {
return false
}
if len(line) >= 1 && bytes.Equal(line[:1], []byte(" ")) {
return true
}
return false
}
// +fmt.Println("ok")
func isHunkPlusLine(isStaged bool, latestState ParseState, line []byte) bool {
if !(latestState == HunkLineNumberLine || latestState == HunkContentLine) {
return false
}
if len(line) >= 1 && bytes.Equal(line[:1], []byte("+")) {
return true
}
return false
}
// -fmt.Println("ok")
func isHunkMinusLine(isStaged bool, latestState ParseState, line []byte) bool {
if !(latestState == HunkLineNumberLine || latestState == HunkContentLine) {
return false
}
if len(line) >= 1 && bytes.Equal(line[:1], []byte("-")) {
return true
}
return false
}
// \ No newline at end of file
func isHunkNewlineWarningLine(isStaged bool, latestState ParseState, line []byte) bool {
if latestState != HunkContentLine {
return false
}
if len(line) >= 27 && bytes.Equal(line[:27], []byte("\\ No newline at end of file")) {
return true
}
return false
}
// Newline after hunk, or an empty line, e.g.
// +}
//
// commit 00920984e3435057f09cee5468850f7546dfa637 (tag: v3.42.0)
func isHunkEmptyLine(isStaged bool, latestState ParseState, line []byte) bool {
if !(latestState == HunkLineNumberLine || latestState == HunkContentLine) {
return false
}
// TODO: Can this also be `\n\r`?
if len(line) == 1 && bytes.Equal(line[:1], []byte("\n")) {
return true
}
return false
}
func isCommitSeparatorLine(isStaged bool, latestState ParseState, line []byte) bool {
if (latestState == ModeLine || latestState == IndexLine || latestState == BinaryFileLine || latestState == ToFileLine) &&
len(line) == 1 && bytes.Equal(line[:1], []byte("\n")) {
return true
}
return false
}
func cleanupParse(currentCommit *Commit, currentDiff *Diff, commitChan chan Commit, totalLogSize *int) {
// Ignore empty or binary diffs (this condition may be redundant).
if currentDiff != nil && currentDiff.Content.Len() > 0 {
currentCommit.Diffs = append(currentCommit.Diffs, *currentDiff)
}
if currentCommit != nil {
commitChan <- *currentCommit
if totalLogSize != nil {
*totalLogSize += currentCommit.Size
}
}
}
File diff suppressed because it is too large Load Diff
+20 -15
View File
@@ -6,12 +6,14 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"strings"
"time" "time"
"github.com/mholt/archiver/v4" "github.com/mholt/archiver/v4"
"github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/common"
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context" logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
) )
type ctxKey int type ctxKey int
@@ -52,14 +54,14 @@ func SetArchiveMaxTimeout(timeout time.Duration) {
} }
// FromFile extracts the files from an archive. // FromFile extracts the files from an archive.
func (d *Archive) FromFile(originalCtx context.Context, data io.Reader) chan ([]byte) { func (d *Archive) FromFile(originalCtx context.Context, data io.Reader) chan ChunkOpt {
archiveChan := make(chan ([]byte), 512) archiveChan := make(chan ChunkOpt, 512)
go func() { go func() {
ctx, cancel := context.WithTimeout(originalCtx, maxTimeout) ctx, cancel := context.WithTimeout(originalCtx, maxTimeout)
logger := logContext.AddLogger(ctx).Logger() logger := logContext.AddLogger(ctx).Logger()
defer cancel() defer cancel()
defer close(archiveChan) defer close(archiveChan)
err := d.openArchive(ctx, 0, data, archiveChan) err := d.openArchive(ctx, nil, data, archiveChan)
if err != nil { if err != nil {
if errors.Is(err, archiver.ErrNoMatch) { if errors.Is(err, archiver.ErrNoMatch) {
return return
@@ -71,18 +73,21 @@ func (d *Archive) FromFile(originalCtx context.Context, data io.Reader) chan ([]
} }
// openArchive takes a reader and extracts the contents up to the maximum depth. // openArchive takes a reader and extracts the contents up to the maximum depth.
func (d *Archive) openArchive(ctx context.Context, depth int, reader io.Reader, archiveChan chan ([]byte)) error { func (d *Archive) openArchive(ctx context.Context, depth []string, reader io.Reader, archiveChan chan ChunkOpt) error {
if depth >= maxDepth { if len(depth) >= maxDepth {
return fmt.Errorf("max archive depth reached") return fmt.Errorf("max archive depth reached")
} }
format, reader, err := archiver.Identify("", reader) format, reader, err := archiver.Identify("", reader)
if err != nil { if err != nil {
if errors.Is(err, archiver.ErrNoMatch) && depth > 0 { if errors.Is(err, archiver.ErrNoMatch) && len(depth) > 0 {
chunkSize := 10 * 1024 chunkSize := 10 * 1024
for { for {
chunk := make([]byte, chunkSize) data := make([]byte, chunkSize)
n, _ := reader.Read(chunk) n, _ := reader.Read(data)
archiveChan <- chunk archiveChan <- func(chunk *sources.Chunk) {
chunk.Data = data
chunk.HandleMetadata = map[string]string{"archive path": strings.Join(depth, " > ")}
}
if n < chunkSize { if n < chunkSize {
break break
} }
@@ -102,9 +107,9 @@ func (d *Archive) openArchive(ctx context.Context, depth int, reader io.Reader,
return err return err
} }
newReader := bytes.NewReader(fileBytes) newReader := bytes.NewReader(fileBytes)
return d.openArchive(ctx, depth+1, newReader, archiveChan) return d.openArchive(ctx, append(depth, "decompressed"), newReader, archiveChan)
case archiver.Extractor: case archiver.Extractor:
err := archive.Extract(context.WithValue(ctx, depthKey, depth+1), reader, nil, d.extractorHandler(archiveChan)) err := archive.Extract(context.WithValue(ctx, depthKey, depth), reader, nil, d.extractorHandler(archiveChan))
if err != nil { if err != nil {
return err return err
} }
@@ -129,12 +134,12 @@ func (d *Archive) IsFiletype(ctx context.Context, reader io.Reader) (io.Reader,
} }
// extractorHandler is applied to each file in an archiver.Extractor file. // extractorHandler is applied to each file in an archiver.Extractor file.
func (d *Archive) extractorHandler(archiveChan chan ([]byte)) func(context.Context, archiver.File) error { func (d *Archive) extractorHandler(archiveChan chan ChunkOpt) func(context.Context, archiver.File) error {
return func(ctx context.Context, f archiver.File) error { return func(ctx context.Context, f archiver.File) error {
logger := logContext.AddLogger(ctx).Logger() logger := logContext.AddLogger(ctx).Logger()
logger.V(5).Info("Handling extracted file.", "filename", f.Name()) logger.V(5).Info("Handling extracted file.", "filename", f.Name())
depth := 0 var depth []string
if ctxDepth, ok := ctx.Value(depthKey).(int); ok { if ctxDepth, ok := ctx.Value(depthKey).([]string); ok {
depth = ctxDepth depth = ctxDepth
} }
@@ -148,7 +153,7 @@ func (d *Archive) extractorHandler(archiveChan chan ([]byte)) func(context.Conte
} }
fileContent := bytes.NewReader(fileBytes) fileContent := bytes.NewReader(fileBytes)
err = d.openArchive(ctx, depth, fileContent, archiveChan) err = d.openArchive(ctx, append(depth, f.Name()), fileContent, archiveChan)
if err != nil { if err != nil {
return err return err
} }
+4 -2
View File
@@ -85,9 +85,11 @@ func TestArchiveHandler(t *testing.T) {
count := 0 count := 0
re := regexp.MustCompile(testCase.matchString) re := regexp.MustCompile(testCase.matchString)
matched := false matched := false
for chunk := range archiveChan { var chunk sources.Chunk
for opt := range archiveChan {
count++ count++
if re.Match(chunk) { opt(&chunk)
if re.Match(chunk.Data) {
matched = true matched = true
} }
} }
+5 -3
View File
@@ -13,8 +13,10 @@ func DefaultHandlers() []Handler {
} }
} }
type ChunkOpt func(*sources.Chunk)
type Handler interface { type Handler interface {
FromFile(context.Context, io.Reader) chan ([]byte) FromFile(context.Context, io.Reader) chan ChunkOpt
IsFiletype(context.Context, io.Reader) (io.Reader, bool) IsFiletype(context.Context, io.Reader) (io.Reader, bool)
New() New()
} }
@@ -38,13 +40,13 @@ func HandleFile(ctx context.Context, file io.Reader, chunkSkel *sources.Chunk, c
handlerChan := handler.FromFile(ctx, file) handlerChan := handler.FromFile(ctx, file)
for { for {
select { select {
case data, open := <-handlerChan: case opt, open := <-handlerChan:
if !open { if !open {
// We finished reading everything from handlerChan. // We finished reading everything from handlerChan.
return true return true
} }
chunk := *chunkSkel chunk := *chunkSkel
chunk.Data = data opt(&chunk)
// Send data on chunksChan. // Send data on chunksChan.
select { select {
case chunksChan <- &chunk: case chunksChan <- &chunk:
+4 -4
View File
@@ -96,9 +96,9 @@ func structToMap(obj interface{}) (m map[string]map[string]interface{}, err erro
} }
type outputFormat struct { type outputFormat struct {
DetectorType, DetectorType string
DecoderType string DecoderType string
Verified bool Verified bool
Raw string Raw string
*source_metadatapb.MetaData *source_metadatapb.MetaData
} }
+15 -9
View File
@@ -12,6 +12,8 @@ const (
ChunkSize = 10 * 1024 ChunkSize = 10 * 1024
// PeekSize is the size of the peek into the previous chunk. // PeekSize is the size of the peek into the previous chunk.
PeekSize = 3 * 1024 PeekSize = 3 * 1024
// TotalChunkSize is the total size of a chunk with peek data.
TotalChunkSize = ChunkSize + PeekSize
) )
// Chunker takes a chunk and splits it into chunks of ChunkSize. // Chunker takes a chunk and splits it into chunks of ChunkSize.
@@ -19,27 +21,31 @@ func Chunker(originalChunk *Chunk) chan *Chunk {
chunkChan := make(chan *Chunk) chunkChan := make(chan *Chunk)
go func() { go func() {
defer close(chunkChan) defer close(chunkChan)
if len(originalChunk.Data) <= ChunkSize+PeekSize { if len(originalChunk.Data) <= TotalChunkSize {
chunkChan <- originalChunk chunkChan <- originalChunk
return return
} }
r := bytes.NewReader(originalChunk.Data) r := bytes.NewReader(originalChunk.Data)
reader := bufio.NewReaderSize(bufio.NewReader(r), ChunkSize) reader := bufio.NewReaderSize(bufio.NewReader(r), ChunkSize)
for { for {
chunkBytes := make([]byte, ChunkSize) chunkBytes := make([]byte, TotalChunkSize)
chunk := *originalChunk chunk := *originalChunk
chunkBytes = chunkBytes[:ChunkSize]
n, err := reader.Read(chunkBytes) n, err := reader.Read(chunkBytes)
if err != nil && !errors.Is(err, io.EOF) { if err != nil && !errors.Is(err, io.EOF) {
break break
} }
if n == 0 {
if errors.Is(err, io.EOF) {
break
}
continue
}
peekData, _ := reader.Peek(PeekSize) peekData, _ := reader.Peek(PeekSize)
chunk.Data = append(chunkBytes[:n], peekData...) copy(chunkBytes[n:], peekData)
if n > 0 { chunk.Data = chunkBytes[:n+len(peekData)]
chunkChan <- &chunk
} chunkChan <- &chunk
if errors.Is(err, io.EOF) {
break
}
} }
}() }()
return chunkChan return chunkChan
+12
View File
@@ -72,3 +72,15 @@ func TestChunker(t *testing.T) {
} }
} }
func BenchmarkChunker(b *testing.B) {
data := bytes.Repeat([]byte("a"), ChunkSize*100)
chunk := &Chunk{
Data: data,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for range Chunker(chunk) {
}
}
}
+10
View File
@@ -51,9 +51,14 @@ type Git struct {
jobID int64 jobID int64
sourceMetadataFunc func(file, email, commit, timestamp, repository string, line int64) *source_metadatapb.MetaData sourceMetadataFunc func(file, email, commit, timestamp, repository string, line int64) *source_metadatapb.MetaData
verify bool verify bool
metrics metrics
concurrency *semaphore.Weighted concurrency *semaphore.Weighted
} }
type metrics struct {
commitsScanned int
}
func NewGit(sourceType sourcespb.SourceType, jobID, sourceID int64, sourceName string, verify bool, concurrency int, func NewGit(sourceType sourcespb.SourceType, jobID, sourceID int64, sourceName string, verify bool, concurrency int,
sourceMetadataFunc func(file, email, commit, timestamp, repository string, line int64) *source_metadatapb.MetaData, sourceMetadataFunc func(file, email, commit, timestamp, repository string, line int64) *source_metadatapb.MetaData,
) *Git { ) *Git {
@@ -339,6 +344,10 @@ func CloneRepoUsingSSH(ctx context.Context, gitUrl string, args ...string) (stri
return CloneRepo(ctx, userInfo, gitUrl, args...) return CloneRepo(ctx, userInfo, gitUrl, args...)
} }
func (s *Git) CommitsScanned() int {
return s.metrics.commitsScanned
}
func (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string, scanOptions *ScanOptions, chunksChan chan *sources.Chunk) error { func (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string, scanOptions *ScanOptions, chunksChan chan *sources.Chunk) error {
if err := GitCmdCheck(); err != nil { if err := GitCmdCheck(); err != nil {
return err return err
@@ -371,6 +380,7 @@ func (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string
break break
} }
depth++ depth++
s.metrics.commitsScanned++
logger.V(5).Info("scanning commit", "commit", commit.Hash) logger.V(5).Info("scanning commit", "commit", commit.Hash)
for _, diff := range commit.Diffs { for _, diff := range commit.Diffs {
if !scanOptions.Filter.Pass(diff.PathB) { if !scanOptions.Filter.Pass(diff.PathB) {
+2 -1
View File
@@ -21,7 +21,8 @@ type Chunk struct {
SourceType sourcespb.SourceType SourceType sourcespb.SourceType
// SourceMetadata holds the context of where the Chunk was found. // SourceMetadata holds the context of where the Chunk was found.
SourceMetadata *source_metadatapb.MetaData SourceMetadata *source_metadatapb.MetaData
// HandleMetadata holds the metadata from a handler if one was used.
HandleMetadata map[string]string
// Data is the data to decode and scan. // Data is the data to decode and scan.
Data []byte Data []byte
// Verify specifies whether any secrets in the Chunk should be verified. // Verify specifies whether any secrets in the Chunk should be verified.