Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0752eae2c | |||
| c6597969eb | |||
| f48a635c34 | |||
| 91cbca941a | |||
| 1a1977f7e6 | |||
| f39303495a | |||
| b5b01d3eba | |||
| 85f363f093 | |||
| 302c3ce8d1 | |||
| fd6c281589 |
@@ -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
|
||||||
|
|||||||
@@ -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=
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -30,6 +31,7 @@ type logCtx struct {
|
|||||||
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 {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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{
|
}
|
||||||
AsciiCaseInsensitive: true,
|
e.prefilter = *ahocorasick.NewTrieBuilder().AddStrings(keywords).Build()
|
||||||
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)
|
||||||
|
|||||||
+350
-109
@@ -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 ||
|
||||||
if currentCommit != nil {
|
latestState == MessageEndLine ||
|
||||||
commitChan <- *currentCommit
|
latestState == ModeLine ||
|
||||||
if totalLogSize != nil {
|
latestState == IndexLine ||
|
||||||
*totalLogSize += currentCommit.Size
|
latestState == BinaryFileLine ||
|
||||||
}
|
latestState == ToFileLine ||
|
||||||
}
|
latestState == HunkContentLine ||
|
||||||
|
latestState == ParseFailure) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
// index 1ed6fbee1..aea1e643a 100644
|
if len(strings.TrimRight(string(line[:]), "\r\n")) == 0 {
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1757
-214
File diff suppressed because it is too large
Load Diff
+20
-15
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
+1
-1
@@ -96,7 +96,7 @@ 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
|
||||||
|
|||||||
+13
-7
@@ -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
|
||||||
}
|
}
|
||||||
peekData, _ := reader.Peek(PeekSize)
|
if n == 0 {
|
||||||
chunk.Data = append(chunkBytes[:n], peekData...)
|
|
||||||
if n > 0 {
|
|
||||||
chunkChan <- &chunk
|
|
||||||
}
|
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
peekData, _ := reader.Peek(PeekSize)
|
||||||
|
copy(chunkBytes[n:], peekData)
|
||||||
|
chunk.Data = chunkBytes[:n+len(peekData)]
|
||||||
|
|
||||||
|
chunkChan <- &chunk
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return chunkChan
|
return chunkChan
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user