From 0f9275e22cb8376b249a43fafecc3a65983dc533 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 28 Feb 2020 11:16:40 -0500 Subject: [PATCH 001/226] update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c7cf32c6..7b5b9dee 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ gitrob.exe vendor/ build/ +.vscode # Test binary, build with `go test -c` *.test From 58cf0441443db43348c5d22c3c00b72b1cdfb767 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 28 Feb 2020 11:42:47 -0500 Subject: [PATCH 002/226] update const name --- core/session.go | 335 ++++++++++++++++++++++++------------------------ 1 file changed, 167 insertions(+), 168 deletions(-) diff --git a/core/session.go b/core/session.go index bf58134e..22c37e51 100644 --- a/core/session.go +++ b/core/session.go @@ -1,242 +1,241 @@ package core import ( - "context" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "os" - "runtime" - "sync" - "time" - - "github.com/gin-gonic/gin" - "github.com/google/go-github/github" - "golang.org/x/oauth2" + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "runtime" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/google/go-github/github" + "golang.org/x/oauth2" ) const ( AccessTokenEnvVariable = "GITROB_ACCESS_TOKEN" - StatusInitializing = "initializing" - StatusGathering = "gathering" - StatusAnalyzing = "analyzing" - StatusFinished = "finished" + StatusInitializing = "initializing" + StatusGathering = "gathering" + StatusAnalyzing = "analyzing" + StatusFinished = "finished" ) type Stats struct { - sync.Mutex + sync.Mutex - StartedAt time.Time - FinishedAt time.Time - Status string - Progress float64 - Targets int - Repositories int - Commits int - Files int - Findings int + StartedAt time.Time + FinishedAt time.Time + Status string + Progress float64 + Targets int + Repositories int + Commits int + Files int + Findings int } type Session struct { - sync.Mutex + sync.Mutex - Version string - Options Options `json:"-"` - Out *Logger `json:"-"` - Stats *Stats - GithubAccessToken string `json:"-"` - GithubClient *github.Client `json:"-"` - Router *gin.Engine `json:"-"` - Targets []*GithubOwner - Repositories []*GithubRepository - Findings []*Finding + Version string + Options Options `json:"-"` + Out *Logger `json:"-"` + Stats *Stats + GithubAccessToken string `json:"-"` + GithubClient *github.Client `json:"-"` + Router *gin.Engine `json:"-"` + Targets []*GithubOwner + Repositories []*GithubRepository + Findings []*Finding } func (s *Session) Start() { - s.InitStats() - s.InitLogger() - s.InitThreads() - s.InitGithubAccessToken() - s.InitGithubClient() - s.InitRouter() + s.InitStats() + s.InitLogger() + s.InitThreads() + s.InitGithubAccessToken() + s.InitGithubClient() + s.InitRouter() } func (s *Session) Finish() { - s.Stats.FinishedAt = time.Now() - s.Stats.Status = StatusFinished + s.Stats.FinishedAt = time.Now() + s.Stats.Status = StatusFinished } func (s *Session) AddTarget(target *GithubOwner) { - s.Lock() - defer s.Unlock() - for _, t := range s.Targets { - if *target.ID == *t.ID { - return - } - } - s.Targets = append(s.Targets, target) + s.Lock() + defer s.Unlock() + for _, t := range s.Targets { + if *target.ID == *t.ID { + return + } + } + s.Targets = append(s.Targets, target) } func (s *Session) AddRepository(repository *GithubRepository) { - s.Lock() - defer s.Unlock() - for _, r := range s.Repositories { - if *repository.ID == *r.ID { - return - } - } - s.Repositories = append(s.Repositories, repository) + s.Lock() + defer s.Unlock() + for _, r := range s.Repositories { + if *repository.ID == *r.ID { + return + } + } + s.Repositories = append(s.Repositories, repository) } func (s *Session) AddFinding(finding *Finding) { - s.Lock() - defer s.Unlock() - s.Findings = append(s.Findings, finding) + s.Lock() + defer s.Unlock() + s.Findings = append(s.Findings, finding) } func (s *Session) InitStats() { - if s.Stats != nil { - return - } - s.Stats = &Stats{ - StartedAt: time.Now(), - Status: StatusInitializing, - Progress: 0.0, - Targets: 0, - Repositories: 0, - Commits: 0, - Files: 0, - Findings: 0, - } + if s.Stats != nil { + return + } + s.Stats = &Stats{ + StartedAt: time.Now(), + Status: StatusInitializing, + Progress: 0.0, + Targets: 0, + Repositories: 0, + Commits: 0, + Files: 0, + Findings: 0, + } } func (s *Session) InitLogger() { - s.Out = &Logger{} - s.Out.SetDebug(*s.Options.Debug) - s.Out.SetSilent(*s.Options.Silent) + s.Out = &Logger{} + s.Out.SetDebug(*s.Options.Debug) + s.Out.SetSilent(*s.Options.Silent) } func (s *Session) InitGithubAccessToken() { - if *s.Options.GithubAccessToken == "" { - accessToken := os.Getenv(AccessTokenEnvVariable) - if accessToken == "" { - s.Out.Fatal("No GitHub access token given. Please provide via command line option or in the %s environment variable.\n", AccessTokenEnvVariable) - } - s.GithubAccessToken = accessToken - } else { - s.GithubAccessToken = *s.Options.GithubAccessToken - } + if *s.Options.GithubAccessToken == "" { + accessToken := os.Getenv(GitHubAccessTokenEnvVariable) + if accessToken == "" { + } + s.GithubAccessToken = accessToken + } else { + s.GithubAccessToken = *s.Options.GithubAccessToken + } } func (s *Session) InitGithubClient() { - ctx := context.Background() - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: s.GithubAccessToken}, - ) - tc := oauth2.NewClient(ctx, ts) - s.GithubClient = github.NewClient(tc) - s.GithubClient.UserAgent = fmt.Sprintf("%s v%s", Name, Version) + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: s.GithubAccessToken}, + ) + tc := oauth2.NewClient(ctx, ts) + s.GithubClient = github.NewClient(tc) + s.GithubClient.UserAgent = fmt.Sprintf("%s v%s", Name, Version) } func (s *Session) InitThreads() { - if *s.Options.Threads == 0 { - numCPUs := runtime.NumCPU() - s.Options.Threads = &numCPUs - } - runtime.GOMAXPROCS(*s.Options.Threads + 2) // thread count + main + web server + if *s.Options.Threads == 0 { + numCPUs := runtime.NumCPU() + s.Options.Threads = &numCPUs + } + runtime.GOMAXPROCS(*s.Options.Threads + 2) // thread count + main + web server } func (s *Session) InitRouter() { - bind := fmt.Sprintf("%s:%d", *s.Options.BindAddress, *s.Options.Port) - s.Router = NewRouter(s) - go func(sess *Session) { - if err := sess.Router.Run(bind); err != nil { - sess.Out.Fatal("Error when starting web server: %s\n", err) - } - }(s) + bind := fmt.Sprintf("%s:%d", *s.Options.BindAddress, *s.Options.Port) + s.Router = NewRouter(s) + go func(sess *Session) { + if err := sess.Router.Run(bind); err != nil { + sess.Out.Fatal("Error when starting web server: %s\n", err) + } + }(s) } func (s *Session) SaveToFile(location string) error { - sessionJson, err := json.Marshal(s) - if err != nil { - return err - } - err = ioutil.WriteFile(location, sessionJson, 0644) - if err != nil { - return err - } - return nil + sessionJson, err := json.Marshal(s) + if err != nil { + return err + } + err = ioutil.WriteFile(location, sessionJson, 0644) + if err != nil { + return err + } + return nil } func (s *Stats) IncrementTargets() { - s.Lock() - defer s.Unlock() - s.Targets++ + s.Lock() + defer s.Unlock() + s.Targets++ } func (s *Stats) IncrementRepositories() { - s.Lock() - defer s.Unlock() - s.Repositories++ + s.Lock() + defer s.Unlock() + s.Repositories++ } func (s *Stats) IncrementCommits() { - s.Lock() - defer s.Unlock() - s.Commits++ + s.Lock() + defer s.Unlock() + s.Commits++ } func (s *Stats) IncrementFiles() { - s.Lock() - defer s.Unlock() - s.Files++ + s.Lock() + defer s.Unlock() + s.Files++ } func (s *Stats) IncrementFindings() { - s.Lock() - defer s.Unlock() - s.Findings++ + s.Lock() + defer s.Unlock() + s.Findings++ } func (s *Stats) UpdateProgress(current int, total int) { - s.Lock() - defer s.Unlock() - if current >= total { - s.Progress = 100.0 - } else { - s.Progress = (float64(current) * float64(100)) / float64(total) - } + s.Lock() + defer s.Unlock() + if current >= total { + s.Progress = 100.0 + } else { + s.Progress = (float64(current) * float64(100)) / float64(total) + } } func NewSession() (*Session, error) { - var err error - var session Session - - if session.Options, err = ParseOptions(); err != nil { - return nil, err - } - - if *session.Options.Save != "" && FileExists(*session.Options.Save) { - return nil, errors.New(fmt.Sprintf("File: %s already exists.", *session.Options.Save)) - } - - if *session.Options.Load != "" { - if !FileExists(*session.Options.Load) { - return nil, errors.New(fmt.Sprintf("Session file %s does not exist or is not readable.", *session.Options.Load)) - } - data, err := ioutil.ReadFile(*session.Options.Load) - if err != nil { - return nil, err - } - if err := json.Unmarshal(data, &session); err != nil { - return nil, errors.New(fmt.Sprintf("Session file %s is corrupt or generated by an old version of Gitrob.", *session.Options.Load)) - } - } - - session.Version = Version - session.Start() - - return &session, nil + var err error + var session Session + + if session.Options, err = ParseOptions(); err != nil { + return nil, err + } + + if *session.Options.Save != "" && FileExists(*session.Options.Save) { + return nil, errors.New(fmt.Sprintf("File: %s already exists.", *session.Options.Save)) + } + + if *session.Options.Load != "" { + if !FileExists(*session.Options.Load) { + return nil, errors.New(fmt.Sprintf("Session file %s does not exist or is not readable.", *session.Options.Load)) + } + data, err := ioutil.ReadFile(*session.Options.Load) + if err != nil { + return nil, err + } + if err := json.Unmarshal(data, &session); err != nil { + return nil, errors.New(fmt.Sprintf("Session file %s is corrupt or generated by an old version of Gitrob.", *session.Options.Load)) + } + } + + session.Version = Version + session.Start() + + return &session, nil } From 401feff3f4c3c938623ee5052ea1ee3b22c5fe84 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 28 Feb 2020 11:48:33 -0500 Subject: [PATCH 003/226] update readme for new env var name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b093ae0d..d5faf8da 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,6 @@ This command will download gitrob, install its dependencies, compile it and move Gitrob will need a Github access token in order to interact with the Github API. [Create a personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/) and save it in an environment variable in your `.bashrc` or similar shell configuration file: - export GITROB_ACCESS_TOKEN=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef + export GITROB_GITHUB_ACCESS_TOKEN=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef Alternatively you can specify the access token with the `-github-access-token` option, but watch out for your command history! From 1df052f8bdac1308c7faa2be114a773e8825aaab Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 28 Feb 2020 13:29:52 -0500 Subject: [PATCH 004/226] add gitlab access token option --- core/options.go | 1 + core/session.go | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/core/options.go b/core/options.go index cf610816..8e0211a3 100644 --- a/core/options.go +++ b/core/options.go @@ -7,6 +7,7 @@ import ( type Options struct { CommitDepth *int GithubAccessToken *string `json:"-"` + GitLabAccessToken *string `json:"-"` NoExpandOrgs *bool Threads *int Save *string `json:"-"` diff --git a/core/session.go b/core/session.go index 22c37e51..f285cbae 100644 --- a/core/session.go +++ b/core/session.go @@ -18,11 +18,11 @@ import ( const ( AccessTokenEnvVariable = "GITROB_ACCESS_TOKEN" - - StatusInitializing = "initializing" - StatusGathering = "gathering" - StatusAnalyzing = "analyzing" - StatusFinished = "finished" + GitLabAccessTokenEnvVariable = "GITROB_GITLAB_ACCESS_TOKEN" + StatusInitializing = "initializing" + StatusGathering = "gathering" + StatusAnalyzing = "analyzing" + StatusFinished = "finished" ) type Stats struct { @@ -47,6 +47,7 @@ type Session struct { Out *Logger `json:"-"` Stats *Stats GithubAccessToken string `json:"-"` + GitLabAccessToken string `json:"-"` GithubClient *github.Client `json:"-"` Router *gin.Engine `json:"-"` Targets []*GithubOwner @@ -58,7 +59,7 @@ func (s *Session) Start() { s.InitStats() s.InitLogger() s.InitThreads() - s.InitGithubAccessToken() + s.InitAccessToken() s.InitGithubClient() s.InitRouter() } @@ -118,15 +119,15 @@ func (s *Session) InitLogger() { s.Out.SetSilent(*s.Options.Silent) } -func (s *Session) InitGithubAccessToken() { +func (s *Session) InitAccessToken() { if *s.Options.GithubAccessToken == "" { - accessToken := os.Getenv(GitHubAccessTokenEnvVariable) - if accessToken == "" { - } - s.GithubAccessToken = accessToken + s.GithubAccessToken = os.Getenv(GitHubAccessTokenEnvVariable) } else { s.GithubAccessToken = *s.Options.GithubAccessToken } + if *s.Options.GitLabAccessToken == "" { + s.GitLabAccessToken = os.Getenv(GitLabAccessTokenEnvVariable) + } } func (s *Session) InitGithubClient() { From 437e56fdb2d5700bebaf4ca857bdf96ec1634bff Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 28 Feb 2020 14:33:49 -0500 Subject: [PATCH 005/226] get gitlab token from options if its not in the env vars --- core/session.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/session.go b/core/session.go index f285cbae..22dac5cf 100644 --- a/core/session.go +++ b/core/session.go @@ -127,6 +127,9 @@ func (s *Session) InitAccessToken() { } if *s.Options.GitLabAccessToken == "" { s.GitLabAccessToken = os.Getenv(GitLabAccessTokenEnvVariable) + } else { + s.GitLabAccessToken = *s.Options.GitLabAccessToken + } } } From be37aaeabc59c6d6cb8893efa1391ab985dded73 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 28 Feb 2020 14:34:03 -0500 Subject: [PATCH 006/226] error out if both types of tokens are set --- core/session.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/session.go b/core/session.go index 22dac5cf..24094a02 100644 --- a/core/session.go +++ b/core/session.go @@ -130,6 +130,8 @@ func (s *Session) InitAccessToken() { } else { s.GitLabAccessToken = *s.Options.GitLabAccessToken } + if s.GitLabAccessToken != "" && s.GithubAccessToken != "" { + s.Out.Fatal("Both a Github and GitLab token are set. Please specify only one.") } } From dc2022902e7abafa183c16427c330149ac1733db Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 28 Feb 2020 15:34:58 -0500 Subject: [PATCH 007/226] fix package ref --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index a693ad89..eefdfd95 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/michenriksen/gitrob/core" + "github.com/codeEmitter/gitrob/core" ) var ( From 059a7132c2f233ea219e7c349f7bd9553790f2a2 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 28 Feb 2020 15:55:24 -0500 Subject: [PATCH 008/226] add gitlab token option definition --- core/options.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/options.go b/core/options.go index 8e0211a3..3df33dbc 100644 --- a/core/options.go +++ b/core/options.go @@ -23,6 +23,7 @@ func ParseOptions() (Options, error) { options := Options{ CommitDepth: flag.Int("commit-depth", 500, "Number of repository commits to process"), GithubAccessToken: flag.String("github-access-token", "", "GitHub access token to use for API requests"), + GitLabAccessToken: flag.String("gitlab-access-token", "", "GitLab access token to use for API requests"), NoExpandOrgs: flag.Bool("no-expand-orgs", false, "Don't add members to targets when processing organizations"), Threads: flag.Int("threads", 0, "Number of concurrent threads (default number of logical CPUs)"), Save: flag.String("save", "", "Save session to file"), From 97a25394f55016839619761a0d26891a427ed21f Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 28 Feb 2020 18:48:59 -0500 Subject: [PATCH 009/226] handle error cases unique to multiple PATs being present --- core/session.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/session.go b/core/session.go index 24094a02..3aa5be57 100644 --- a/core/session.go +++ b/core/session.go @@ -131,7 +131,10 @@ func (s *Session) InitAccessToken() { s.GitLabAccessToken = *s.Options.GitLabAccessToken } if s.GitLabAccessToken != "" && s.GithubAccessToken != "" { - s.Out.Fatal("Both a Github and GitLab token are set. Please specify only one.") + s.Out.Fatal("Both a GitLab and Github token are present. Only one may set.") + } + if s.GitLabAccessToken == "" && s.GithubAccessToken == "" { + s.Out.Fatal("No valid API token was found.") } } From e331014aa9285b76c9fae4fd2e49c63aba41a4a9 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 2 Mar 2020 13:31:44 -0500 Subject: [PATCH 010/226] correct variable name and value --- core/session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/session.go b/core/session.go index 3aa5be57..927fa0f8 100644 --- a/core/session.go +++ b/core/session.go @@ -17,7 +17,7 @@ import ( ) const ( - AccessTokenEnvVariable = "GITROB_ACCESS_TOKEN" + GitHubAccessTokenEnvVariable = "GITROB_GITHUB_ACCESS_TOKEN" GitLabAccessTokenEnvVariable = "GITROB_GITLAB_ACCESS_TOKEN" StatusInitializing = "initializing" StatusGathering = "gathering" From 0814be0c41fcb89ba0638637952362737851ef56 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 2 Mar 2020 13:49:42 -0500 Subject: [PATCH 011/226] standardize go formatting in docs --- core/bindata.go | 3 +- core/core.go | 36 +- core/git.go | 180 +++--- core/github.go | 182 +++---- core/log.go | 86 +-- core/router.go | 198 +++---- core/signatures.go | 1292 ++++++++++++++++++++++---------------------- 7 files changed, 989 insertions(+), 988 deletions(-) diff --git a/core/bindata.go b/core/bindata.go index 36b4b030..89926671 100644 --- a/core/bindata.go +++ b/core/bindata.go @@ -33,13 +33,14 @@ import ( "bytes" "compress/gzip" "fmt" - "github.com/elazarl/go-bindata-assetfs" "io" "io/ioutil" "os" "path/filepath" "strings" "time" + + assetfs "github.com/elazarl/go-bindata-assetfs" ) func bindataRead(data []byte, name string) ([]byte, error) { diff --git a/core/core.go b/core/core.go index 4f950b59..878a350e 100644 --- a/core/core.go +++ b/core/core.go @@ -1,33 +1,33 @@ package core import ( - "fmt" - "os" - "regexp" - "strings" + "fmt" + "os" + "regexp" + "strings" ) var NewlineRegex = regexp.MustCompile(`\r?\n`) func FileExists(path string) bool { - if _, err := os.Stat(path); os.IsNotExist(err) { - return false - } - return true + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } + return true } func Pluralize(count int, singular string, plural string) string { - if count == 1 { - return singular - } - return plural + if count == 1 { + return singular + } + return plural } func TruncateString(str string, maxLength int) string { - str = NewlineRegex.ReplaceAllString(str, " ") - str = strings.TrimSpace(str) - if len(str) > maxLength { - str = fmt.Sprintf("%s...", str[0:maxLength]) - } - return str + str = NewlineRegex.ReplaceAllString(str, " ") + str = strings.TrimSpace(str) + if len(str) > maxLength { + str = fmt.Sprintf("%s...", str[0:maxLength]) + } + return str } diff --git a/core/git.go b/core/git.go index f5abbc5c..2e780898 100644 --- a/core/git.go +++ b/core/git.go @@ -1,120 +1,120 @@ package core import ( - "fmt" - "io/ioutil" + "fmt" + "io/ioutil" - "gopkg.in/src-d/go-git.v4" - "gopkg.in/src-d/go-git.v4/plumbing" - "gopkg.in/src-d/go-git.v4/plumbing/object" - "gopkg.in/src-d/go-git.v4/utils/merkletrie" + "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/utils/merkletrie" ) const ( - EmptyTreeCommitId = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" + EmptyTreeCommitId = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" ) func CloneRepository(url *string, branch *string, depth int) (*git.Repository, string, error) { - urlVal := *url - branchVal := *branch - dir, err := ioutil.TempDir("", "gitrob") - if err != nil { - return nil, "", err - } - repository, err := git.PlainClone(dir, false, &git.CloneOptions{ - URL: urlVal, - Depth: depth, - ReferenceName: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branchVal)), - SingleBranch: true, - Tags: git.NoTags, - }) - if err != nil { - return nil, dir, err - } - return repository, dir, nil + urlVal := *url + branchVal := *branch + dir, err := ioutil.TempDir("", "gitrob") + if err != nil { + return nil, "", err + } + repository, err := git.PlainClone(dir, false, &git.CloneOptions{ + URL: urlVal, + Depth: depth, + ReferenceName: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branchVal)), + SingleBranch: true, + Tags: git.NoTags, + }) + if err != nil { + return nil, dir, err + } + return repository, dir, nil } func GetRepositoryHistory(repository *git.Repository) ([]*object.Commit, error) { - var commits []*object.Commit - ref, err := repository.Head() - if err != nil { - return nil, err - } - cIter, err := repository.Log(&git.LogOptions{From: ref.Hash()}) - if err != nil { - return nil, err - } - cIter.ForEach(func(c *object.Commit) error { - commits = append(commits, c) - return nil - }) - return commits, nil + var commits []*object.Commit + ref, err := repository.Head() + if err != nil { + return nil, err + } + cIter, err := repository.Log(&git.LogOptions{From: ref.Hash()}) + if err != nil { + return nil, err + } + cIter.ForEach(func(c *object.Commit) error { + commits = append(commits, c) + return nil + }) + return commits, nil } func GetChanges(commit *object.Commit, repo *git.Repository) (object.Changes, error) { - parentCommit, err := GetParentCommit(commit, repo) - if err != nil { - return nil, err - } + parentCommit, err := GetParentCommit(commit, repo) + if err != nil { + return nil, err + } - commitTree, err := commit.Tree() - if err != nil { - return nil, err - } + commitTree, err := commit.Tree() + if err != nil { + return nil, err + } - parentCommitTree, err := parentCommit.Tree() - if err != nil { - return nil, err - } + parentCommitTree, err := parentCommit.Tree() + if err != nil { + return nil, err + } - changes, err := object.DiffTree(parentCommitTree, commitTree) - if err != nil { - return nil, err - } - return changes, nil + changes, err := object.DiffTree(parentCommitTree, commitTree) + if err != nil { + return nil, err + } + return changes, nil } func GetParentCommit(commit *object.Commit, repo *git.Repository) (*object.Commit, error) { - if commit.NumParents() == 0 { - parentCommit, err := repo.CommitObject(plumbing.NewHash(EmptyTreeCommitId)) - if err != nil { - return nil, err - } - return parentCommit, nil - } - parentCommit, err := commit.Parents().Next() - if err != nil { - return nil, err - } - return parentCommit, nil + if commit.NumParents() == 0 { + parentCommit, err := repo.CommitObject(plumbing.NewHash(EmptyTreeCommitId)) + if err != nil { + return nil, err + } + return parentCommit, nil + } + parentCommit, err := commit.Parents().Next() + if err != nil { + return nil, err + } + return parentCommit, nil } func GetChangeAction(change *object.Change) string { - action, err := change.Action() - if err != nil { - return "Unknown" - } - switch action { - case merkletrie.Insert: - return "Insert" - case merkletrie.Modify: - return "Modify" - case merkletrie.Delete: - return "Delete" - default: - return "Unknown" - } + action, err := change.Action() + if err != nil { + return "Unknown" + } + switch action { + case merkletrie.Insert: + return "Insert" + case merkletrie.Modify: + return "Modify" + case merkletrie.Delete: + return "Delete" + default: + return "Unknown" + } } func GetChangePath(change *object.Change) string { - action, err := change.Action() - if err != nil { - return change.To.Name - } + action, err := change.Action() + if err != nil { + return change.To.Name + } - if action == merkletrie.Delete { - return change.From.Name - } else { - return change.To.Name - } + if action == merkletrie.Delete { + return change.From.Name + } else { + return change.To.Name + } } diff --git a/core/github.go b/core/github.go index a1941701..d4db6689 100644 --- a/core/github.go +++ b/core/github.go @@ -1,113 +1,113 @@ package core import ( - "context" + "context" - "github.com/google/go-github/github" + "github.com/google/go-github/github" ) type GithubOwner struct { - Login *string - ID *int64 - Type *string - Name *string - AvatarURL *string - URL *string - Company *string - Blog *string - Location *string - Email *string - Bio *string + Login *string + ID *int64 + Type *string + Name *string + AvatarURL *string + URL *string + Company *string + Blog *string + Location *string + Email *string + Bio *string } type GithubRepository struct { - Owner *string - ID *int64 - Name *string - FullName *string - CloneURL *string - URL *string - DefaultBranch *string - Description *string - Homepage *string + Owner *string + ID *int64 + Name *string + FullName *string + CloneURL *string + URL *string + DefaultBranch *string + Description *string + Homepage *string } func GetUserOrOrganization(login string, client *github.Client) (*GithubOwner, error) { - ctx := context.Background() - user, _, err := client.Users.Get(ctx, login) - if err != nil { - return nil, err - } - return &GithubOwner{ - Login: user.Login, - ID: user.ID, - Type: user.Type, - Name: user.Name, - AvatarURL: user.AvatarURL, - URL: user.HTMLURL, - Company: user.Company, - Blog: user.Blog, - Location: user.Location, - Email: user.Email, - Bio: user.Bio, - }, nil + ctx := context.Background() + user, _, err := client.Users.Get(ctx, login) + if err != nil { + return nil, err + } + return &GithubOwner{ + Login: user.Login, + ID: user.ID, + Type: user.Type, + Name: user.Name, + AvatarURL: user.AvatarURL, + URL: user.HTMLURL, + Company: user.Company, + Blog: user.Blog, + Location: user.Location, + Email: user.Email, + Bio: user.Bio, + }, nil } func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*GithubRepository, error) { - var allRepos []*GithubRepository - loginVal := *login - ctx := context.Background() - opt := &github.RepositoryListOptions{ - Type: "sources", - } + var allRepos []*GithubRepository + loginVal := *login + ctx := context.Background() + opt := &github.RepositoryListOptions{ + Type: "sources", + } - for { - repos, resp, err := client.Repositories.List(ctx, loginVal, opt) - if err != nil { - return allRepos, err - } - for _, repo := range repos { - if !*repo.Fork { - r := GithubRepository{ - Owner: repo.Owner.Login, - ID: repo.ID, - Name: repo.Name, - FullName: repo.FullName, - CloneURL: repo.CloneURL, - URL: repo.HTMLURL, - DefaultBranch: repo.DefaultBranch, - Description: repo.Description, - Homepage: repo.Homepage, - } - allRepos = append(allRepos, &r) - } - } - if resp.NextPage == 0 { - break - } - opt.Page = resp.NextPage - } + for { + repos, resp, err := client.Repositories.List(ctx, loginVal, opt) + if err != nil { + return allRepos, err + } + for _, repo := range repos { + if !*repo.Fork { + r := GithubRepository{ + Owner: repo.Owner.Login, + ID: repo.ID, + Name: repo.Name, + FullName: repo.FullName, + CloneURL: repo.CloneURL, + URL: repo.HTMLURL, + DefaultBranch: repo.DefaultBranch, + Description: repo.Description, + Homepage: repo.Homepage, + } + allRepos = append(allRepos, &r) + } + } + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } - return allRepos, nil + return allRepos, nil } func GetOrganizationMembers(login *string, client *github.Client) ([]*GithubOwner, error) { - var allMembers []*GithubOwner - loginVal := *login - ctx := context.Background() - opt := &github.ListMembersOptions{} - for { - members, resp, err := client.Organizations.ListMembers(ctx, loginVal, opt) - if err != nil { - return allMembers, err - } - for _, member := range members { - allMembers = append(allMembers, &GithubOwner{Login: member.Login, ID: member.ID, Type: member.Type}) - } - if resp.NextPage == 0 { - break - } - opt.Page = resp.NextPage - } - return allMembers, nil + var allMembers []*GithubOwner + loginVal := *login + ctx := context.Background() + opt := &github.ListMembersOptions{} + for { + members, resp, err := client.Organizations.ListMembers(ctx, loginVal, opt) + if err != nil { + return allMembers, err + } + for _, member := range members { + allMembers = append(allMembers, &GithubOwner{Login: member.Login, ID: member.ID, Type: member.Type}) + } + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + return allMembers, nil } diff --git a/core/log.go b/core/log.go index ce972670..79a889a8 100644 --- a/core/log.go +++ b/core/log.go @@ -1,85 +1,85 @@ package core import ( - "fmt" - "os" - "sync" + "fmt" + "os" + "sync" - "github.com/fatih/color" + "github.com/fatih/color" ) const ( - FATAL = 5 - ERROR = 4 - WARN = 3 - IMPORTANT = 2 - INFO = 1 - DEBUG = 0 + FATAL = 5 + ERROR = 4 + WARN = 3 + IMPORTANT = 2 + INFO = 1 + DEBUG = 0 ) var LogColors = map[int]*color.Color{ - FATAL: color.New(color.FgRed).Add(color.Bold), - ERROR: color.New(color.FgRed), - WARN: color.New(color.FgYellow), - IMPORTANT: color.New(color.Bold), - DEBUG: color.New(color.FgCyan).Add(color.Faint), + FATAL: color.New(color.FgRed).Add(color.Bold), + ERROR: color.New(color.FgRed), + WARN: color.New(color.FgYellow), + IMPORTANT: color.New(color.Bold), + DEBUG: color.New(color.FgCyan).Add(color.Faint), } type Logger struct { - sync.Mutex + sync.Mutex - debug bool - silent bool + debug bool + silent bool } func (l *Logger) SetSilent(s bool) { - l.silent = s + l.silent = s } func (l *Logger) SetDebug(d bool) { - l.debug = d + l.debug = d } func (l *Logger) Log(level int, format string, args ...interface{}) { - l.Lock() - defer l.Unlock() - if level == DEBUG && l.debug == false { - return - } else if level < ERROR && l.silent == true { - return - } - - if c, ok := LogColors[level]; ok { - c.Printf(format, args...) - } else { - fmt.Printf(format, args...) - } - - if level == FATAL { - os.Exit(1) - } + l.Lock() + defer l.Unlock() + if level == DEBUG && l.debug == false { + return + } else if level < ERROR && l.silent == true { + return + } + + if c, ok := LogColors[level]; ok { + c.Printf(format, args...) + } else { + fmt.Printf(format, args...) + } + + if level == FATAL { + os.Exit(1) + } } func (l *Logger) Fatal(format string, args ...interface{}) { - l.Log(FATAL, format, args...) + l.Log(FATAL, format, args...) } func (l *Logger) Error(format string, args ...interface{}) { - l.Log(ERROR, format, args...) + l.Log(ERROR, format, args...) } func (l *Logger) Warn(format string, args ...interface{}) { - l.Log(WARN, format, args...) + l.Log(WARN, format, args...) } func (l *Logger) Important(format string, args ...interface{}) { - l.Log(IMPORTANT, format, args...) + l.Log(IMPORTANT, format, args...) } func (l *Logger) Info(format string, args ...interface{}) { - l.Log(INFO, format, args...) + l.Log(INFO, format, args...) } func (l *Logger) Debug(format string, args ...interface{}) { - l.Log(DEBUG, format, args...) + l.Log(DEBUG, format, args...) } diff --git a/core/router.go b/core/router.go index 227e29bb..175ca23a 100644 --- a/core/router.go +++ b/core/router.go @@ -1,124 +1,124 @@ package core import ( - "fmt" - "io/ioutil" - "net/http" - "strings" - - assetfs "github.com/elazarl/go-bindata-assetfs" - "github.com/gin-contrib/secure" - "github.com/gin-contrib/static" - "github.com/gin-gonic/gin" + "fmt" + "io/ioutil" + "net/http" + "strings" + + assetfs "github.com/elazarl/go-bindata-assetfs" + "github.com/gin-contrib/secure" + "github.com/gin-contrib/static" + "github.com/gin-gonic/gin" ) const ( - GithubBaseUri = "https://raw.githubusercontent.com" - MaximumFileSize = 102400 - CspPolicy = "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'" - ReferrerPolicy = "no-referrer" + GithubBaseUri = "https://raw.githubusercontent.com" + MaximumFileSize = 102400 + CspPolicy = "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'" + ReferrerPolicy = "no-referrer" ) type binaryFileSystem struct { - fs http.FileSystem + fs http.FileSystem } func (b *binaryFileSystem) Open(name string) (http.File, error) { - return b.fs.Open(name) + return b.fs.Open(name) } func (b *binaryFileSystem) Exists(prefix string, filepath string) bool { - if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) { - if _, err := b.fs.Open(p); err != nil { - return false - } - return true - } - return false + if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) { + if _, err := b.fs.Open(p); err != nil { + return false + } + return true + } + return false } func BinaryFileSystem(root string) *binaryFileSystem { - fs := &assetfs.AssetFS{Asset, AssetDir, AssetInfo, root} - return &binaryFileSystem{ - fs, - } + fs := &assetfs.AssetFS{Asset, AssetDir, AssetInfo, root} + return &binaryFileSystem{ + fs, + } } func NewRouter(s *Session) *gin.Engine { - if *s.Options.Debug == true { - gin.SetMode(gin.DebugMode) - } else { - gin.SetMode(gin.ReleaseMode) - } - - router := gin.New() - router.Use(static.Serve("/", BinaryFileSystem("static"))) - router.Use(secure.New(secure.Config{ - SSLRedirect: false, - IsDevelopment: false, - FrameDeny: true, - ContentTypeNosniff: true, - BrowserXssFilter: true, - ContentSecurityPolicy: CspPolicy, - ReferrerPolicy: ReferrerPolicy, - })) - router.GET("/stats", func(c *gin.Context) { - c.JSON(200, s.Stats) - }) - router.GET("/findings", func(c *gin.Context) { - c.JSON(200, s.Findings) - }) - router.GET("/targets", func(c *gin.Context) { - c.JSON(200, s.Targets) - }) - router.GET("/repositories", func(c *gin.Context) { - c.JSON(200, s.Repositories) - }) - router.GET("/files/:owner/:repo/:commit/*path", fetchFile) - - return router + if *s.Options.Debug == true { + gin.SetMode(gin.DebugMode) + } else { + gin.SetMode(gin.ReleaseMode) + } + + router := gin.New() + router.Use(static.Serve("/", BinaryFileSystem("static"))) + router.Use(secure.New(secure.Config{ + SSLRedirect: false, + IsDevelopment: false, + FrameDeny: true, + ContentTypeNosniff: true, + BrowserXssFilter: true, + ContentSecurityPolicy: CspPolicy, + ReferrerPolicy: ReferrerPolicy, + })) + router.GET("/stats", func(c *gin.Context) { + c.JSON(200, s.Stats) + }) + router.GET("/findings", func(c *gin.Context) { + c.JSON(200, s.Findings) + }) + router.GET("/targets", func(c *gin.Context) { + c.JSON(200, s.Targets) + }) + router.GET("/repositories", func(c *gin.Context) { + c.JSON(200, s.Repositories) + }) + router.GET("/files/:owner/:repo/:commit/*path", fetchFile) + + return router } func fetchFile(c *gin.Context) { - fileUrl := fmt.Sprintf("%s/%s/%s/%s%s", GithubBaseUri, c.Param("owner"), c.Param("repo"), c.Param("commit"), c.Param("path")) - resp, err := http.Head(fileUrl) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "message": err, - }) - return - } - - if resp.StatusCode == http.StatusNotFound { - c.JSON(http.StatusNotFound, gin.H{ - "message": "No content", - }) - return - } - - if resp.ContentLength > MaximumFileSize { - c.JSON(http.StatusUnprocessableEntity, gin.H{ - "message": fmt.Sprintf("File size exceeds maximum of %d bytes", MaximumFileSize), - }) - return - } - - resp, err = http.Get(fileUrl) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "message": err, - }) - return - } - - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "message": err, - }) - return - } - - c.String(http.StatusOK, string(body[:])) + fileUrl := fmt.Sprintf("%s/%s/%s/%s%s", GithubBaseUri, c.Param("owner"), c.Param("repo"), c.Param("commit"), c.Param("path")) + resp, err := http.Head(fileUrl) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "message": err, + }) + return + } + + if resp.StatusCode == http.StatusNotFound { + c.JSON(http.StatusNotFound, gin.H{ + "message": "No content", + }) + return + } + + if resp.ContentLength > MaximumFileSize { + c.JSON(http.StatusUnprocessableEntity, gin.H{ + "message": fmt.Sprintf("File size exceeds maximum of %d bytes", MaximumFileSize), + }) + return + } + + resp, err = http.Get(fileUrl) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "message": err, + }) + return + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "message": err, + }) + return + } + + c.String(http.StatusOK, string(body[:])) } diff --git a/core/signatures.go b/core/signatures.go index fe36cc03..4216147a 100644 --- a/core/signatures.go +++ b/core/signatures.go @@ -1,710 +1,710 @@ package core import ( - "crypto/sha1" - "fmt" - "io" - "path/filepath" - "regexp" - "strings" + "crypto/sha1" + "fmt" + "io" + "path/filepath" + "regexp" + "strings" ) const ( - TypeSimple = "simple" - TypePattern = "pattern" + TypeSimple = "simple" + TypePattern = "pattern" - PartExtension = "extension" - PartFilename = "filename" - PartPath = "path" + PartExtension = "extension" + PartFilename = "filename" + PartPath = "path" ) var skippableExtensions = []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".tif", ".psd", ".xcf"} var skippablePathIndicators = []string{"node_modules/", "vendor/bundle", "vendor/cache"} type MatchFile struct { - Path string - Filename string - Extension string + Path string + Filename string + Extension string } func (f *MatchFile) IsSkippable() bool { - ext := strings.ToLower(f.Extension) - path := strings.ToLower(f.Path) - for _, skippableExt := range skippableExtensions { - if ext == skippableExt { - return true - } - } - for _, skippablePathIndicator := range skippablePathIndicators { - if strings.Contains(path, skippablePathIndicator) { - return true - } - } - return false + ext := strings.ToLower(f.Extension) + path := strings.ToLower(f.Path) + for _, skippableExt := range skippableExtensions { + if ext == skippableExt { + return true + } + } + for _, skippablePathIndicator := range skippablePathIndicators { + if strings.Contains(path, skippablePathIndicator) { + return true + } + } + return false } type Finding struct { - Id string - FilePath string - Action string - Description string - Comment string - RepositoryOwner string - RepositoryName string - CommitHash string - CommitMessage string - CommitAuthor string - FileUrl string - CommitUrl string - RepositoryUrl string + Id string + FilePath string + Action string + Description string + Comment string + RepositoryOwner string + RepositoryName string + CommitHash string + CommitMessage string + CommitAuthor string + FileUrl string + CommitUrl string + RepositoryUrl string } func (f *Finding) setupUrls() { - f.RepositoryUrl = fmt.Sprintf("https://github.com/%s/%s", f.RepositoryOwner, f.RepositoryName) - f.FileUrl = fmt.Sprintf("%s/blob/%s/%s", f.RepositoryUrl, f.CommitHash, f.FilePath) - f.CommitUrl = fmt.Sprintf("%s/commit/%s", f.RepositoryUrl, f.CommitHash) + f.RepositoryUrl = fmt.Sprintf("https://github.com/%s/%s", f.RepositoryOwner, f.RepositoryName) + f.FileUrl = fmt.Sprintf("%s/blob/%s/%s", f.RepositoryUrl, f.CommitHash, f.FilePath) + f.CommitUrl = fmt.Sprintf("%s/commit/%s", f.RepositoryUrl, f.CommitHash) } func (f *Finding) generateID() { - h := sha1.New() - io.WriteString(h, f.FilePath) - io.WriteString(h, f.Action) - io.WriteString(h, f.RepositoryOwner) - io.WriteString(h, f.RepositoryName) - io.WriteString(h, f.CommitHash) - io.WriteString(h, f.CommitMessage) - io.WriteString(h, f.CommitAuthor) - f.Id = fmt.Sprintf("%x", h.Sum(nil)) + h := sha1.New() + io.WriteString(h, f.FilePath) + io.WriteString(h, f.Action) + io.WriteString(h, f.RepositoryOwner) + io.WriteString(h, f.RepositoryName) + io.WriteString(h, f.CommitHash) + io.WriteString(h, f.CommitMessage) + io.WriteString(h, f.CommitAuthor) + f.Id = fmt.Sprintf("%x", h.Sum(nil)) } func (f *Finding) Initialize() { - f.setupUrls() - f.generateID() + f.setupUrls() + f.generateID() } type Signature interface { - Match(file MatchFile) bool - Description() string - Comment() string + Match(file MatchFile) bool + Description() string + Comment() string } type SimpleSignature struct { - part string - match string - description string - comment string + part string + match string + description string + comment string } type PatternSignature struct { - part string - match *regexp.Regexp - description string - comment string + part string + match *regexp.Regexp + description string + comment string } func (s SimpleSignature) Match(file MatchFile) bool { - var haystack *string - switch s.part { - case PartPath: - haystack = &file.Path - case PartFilename: - haystack = &file.Filename - case PartExtension: - haystack = &file.Extension - default: - return false - } + var haystack *string + switch s.part { + case PartPath: + haystack = &file.Path + case PartFilename: + haystack = &file.Filename + case PartExtension: + haystack = &file.Extension + default: + return false + } - return (s.match == *haystack) + return (s.match == *haystack) } func (s SimpleSignature) Description() string { - return s.description + return s.description } func (s SimpleSignature) Comment() string { - return s.comment + return s.comment } func (s PatternSignature) Match(file MatchFile) bool { - var haystack *string - switch s.part { - case PartPath: - haystack = &file.Path - case PartFilename: - haystack = &file.Filename - case PartExtension: - haystack = &file.Extension - default: - return false - } + var haystack *string + switch s.part { + case PartPath: + haystack = &file.Path + case PartFilename: + haystack = &file.Filename + case PartExtension: + haystack = &file.Extension + default: + return false + } - return s.match.MatchString(*haystack) + return s.match.MatchString(*haystack) } func (s PatternSignature) Description() string { - return s.description + return s.description } func (s PatternSignature) Comment() string { - return s.comment + return s.comment } func NewMatchFile(path string) MatchFile { - _, filename := filepath.Split(path) - extension := filepath.Ext(path) - return MatchFile{ - Path: path, - Filename: filename, - Extension: extension, - } + _, filename := filepath.Split(path) + extension := filepath.Ext(path) + return MatchFile{ + Path: path, + Filename: filename, + Extension: extension, + } } var Signatures = []Signature{ - SimpleSignature{ - part: PartExtension, - match: ".pem", - description: "Potential cryptographic private key", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".log", - description: "Log file", - comment: "Log files can contain secret HTTP endpoints, session IDs, API keys and other goodies", - }, - SimpleSignature{ - part: PartExtension, - match: ".pkcs12", - description: "Potential cryptographic key bundle", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".p12", - description: "Potential cryptographic key bundle", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".pfx", - description: "Potential cryptographic key bundle", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".asc", - description: "Potential cryptographic key bundle", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "otr.private_key", - description: "Pidgin OTR private key", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".ovpn", - description: "OpenVPN client configuration file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".cscfg", - description: "Azure service configuration schema file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".rdp", - description: "Remote Desktop connection file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".mdf", - description: "Microsoft SQL database file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".sdf", - description: "Microsoft SQL server compact database file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".sqlite", - description: "SQLite database file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".bek", - description: "Microsoft BitLocker recovery key file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".tpm", - description: "Microsoft BitLocker Trusted Platform Module password file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".fve", - description: "Windows BitLocker full volume encrypted data file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".jks", - description: "Java keystore file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".psafe3", - description: "Password Safe database file", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "secret_token.rb", - description: "Ruby On Rails secret token configuration file", - comment: "If the Rails secret token is known, it can allow for remote code execution (http://www.exploit-db.com/exploits/27527/)", - }, - SimpleSignature{ - part: PartFilename, - match: "carrierwave.rb", - description: "Carrierwave configuration file", - comment: "Can contain credentials for cloud storage systems such as Amazon S3 and Google Storage", - }, - SimpleSignature{ - part: PartFilename, - match: "database.yml", - description: "Potential Ruby On Rails database configuration file", - comment: "Can contain database credentials", - }, - SimpleSignature{ - part: PartFilename, - match: "omniauth.rb", - description: "OmniAuth configuration file", - comment: "The OmniAuth configuration file can contain client application secrets", - }, - SimpleSignature{ - part: PartFilename, - match: "settings.py", - description: "Django configuration file", - comment: "Can contain database credentials, cloud storage system credentials, and other secrets", - }, - SimpleSignature{ - part: PartExtension, - match: ".agilekeychain", - description: "1Password password manager database file", - comment: "Feed it to Hashcat and see if you're lucky", - }, - SimpleSignature{ - part: PartExtension, - match: ".keychain", - description: "Apple Keychain database file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".pcap", - description: "Network traffic capture file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".gnucash", - description: "GnuCash database file", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin.xml", - description: "Jenkins publish over SSH plugin file", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "credentials.xml", - description: "Potential Jenkins credentials file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".kwallet", - description: "KDE Wallet Manager database file", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "LocalSettings.php", - description: "Potential MediaWiki configuration file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".tblk", - description: "Tunnelblick VPN configuration file", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "Favorites.plist", - description: "Sequel Pro MySQL database manager bookmark file", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "configuration.user.xpl", - description: "Little Snitch firewall configuration file", - comment: "Contains traffic rules for applications", - }, - SimpleSignature{ - part: PartExtension, - match: ".dayone", - description: "Day One journal file", - comment: "Now it's getting creepy...", - }, - SimpleSignature{ - part: PartFilename, - match: "journal.txt", - description: "Potential jrnl journal file", - comment: "Now it's getting creepy...", - }, - SimpleSignature{ - part: PartFilename, - match: "knife.rb", - description: "Chef Knife configuration file", - comment: "Can contain references to Chef servers", - }, - SimpleSignature{ - part: PartFilename, - match: "proftpdpasswd", - description: "cPanel backup ProFTPd credentials file", - comment: "Contains usernames and password hashes for FTP accounts", - }, - SimpleSignature{ - part: PartFilename, - match: "robomongo.json", - description: "Robomongo MongoDB manager configuration file", - comment: "Can contain credentials for MongoDB databases", - }, - SimpleSignature{ - part: PartFilename, - match: "filezilla.xml", - description: "FileZilla FTP configuration file", - comment: "Can contain credentials for FTP servers", - }, - SimpleSignature{ - part: PartFilename, - match: "recentservers.xml", - description: "FileZilla FTP recent servers file", - comment: "Can contain credentials for FTP servers", - }, - SimpleSignature{ - part: PartFilename, - match: "ventrilo_srv.ini", - description: "Ventrilo server configuration file", - comment: "Can contain passwords", - }, - SimpleSignature{ - part: PartFilename, - match: "terraform.tfvars", - description: "Terraform variable config file", - comment: "Can contain credentials for terraform providers", - }, - SimpleSignature{ - part: PartFilename, - match: ".exports", - description: "Shell configuration file", - comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - SimpleSignature{ - part: PartFilename, - match: ".functions", - description: "Shell configuration file", - comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - SimpleSignature{ - part: PartFilename, - match: ".extra", - description: "Shell configuration file", - comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^.*_rsa$`), - description: "Private SSH key", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^.*_dsa$`), - description: "Private SSH key", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^.*_ed25519$`), - description: "Private SSH key", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^.*_ecdsa$`), - description: "Private SSH key", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?ssh/config$`), - description: "SSH configuration file", - comment: "", - }, - PatternSignature{ - part: PartExtension, - match: regexp.MustCompile(`^key(pair)?$`), - description: "Potential cryptographic private key", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?(bash_|zsh_|sh_|z)?history$`), - description: "Shell command history file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?mysql_history$`), - description: "MySQL client command history file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?psql_history$`), - description: "PostgreSQL client command history file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?pgpass$`), - description: "PostgreSQL password file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?irb_history$`), - description: "Ruby IRB console history file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?purple/accounts\.xml$`), - description: "Pidgin chat client account configuration file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?xchat2?/servlist_?\.conf$`), - description: "Hexchat/XChat IRC client server list configuration file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?irssi/config$`), - description: "Irssi IRC client configuration file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?recon-ng/keys\.db$`), - description: "Recon-ng web reconnaissance framework API key database", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?dbeaver-data-sources.xml$`), - description: "DBeaver SQL database manager configuration file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?muttrc$`), - description: "Mutt e-mail client configuration file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?s3cfg$`), - description: "S3cmd configuration file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?aws/credentials$`), - description: "AWS CLI credentials file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^sftp-config(\.json)?$`), - description: "SFTP connection configuration file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?trc$`), - description: "T command-line Twitter client configuration file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?gitrobrc$`), - description: "Well, this is awkward... Gitrob configuration file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?(bash|zsh|csh)rc$`), - description: "Shell configuration file", - comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?(bash_|zsh_)?profile$`), - description: "Shell profile configuration file", - comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?(bash_|zsh_)?aliases$`), - description: "Shell command alias configuration file", - comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`config(\.inc)?\.php$`), - description: "PHP configuration file", - comment: "", - }, - PatternSignature{ - part: PartExtension, - match: regexp.MustCompile(`^key(store|ring)$`), - description: "GNOME Keyring database file", - comment: "", - }, - PatternSignature{ - part: PartExtension, - match: regexp.MustCompile(`^kdbx?$`), - description: "KeePass password manager database file", - comment: "Feed it to Hashcat and see if you're lucky", - }, - PatternSignature{ - part: PartExtension, - match: regexp.MustCompile(`^sql(dump)?$`), - description: "SQL dump file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?htpasswd$`), - description: "Apache htpasswd file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^(\.|_)?netrc$`), - description: "Configuration file for auto-login process", - comment: "Can contain username and password", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?gem/credentials$`), - description: "Rubygems credentials file", - comment: "Can contain API key for a rubygems.org account", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?tugboat$`), - description: "Tugboat DigitalOcean management tool configuration", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`doctl/config.yaml$`), - description: "DigitalOcean doctl command-line client configuration file", - comment: "Contains DigitalOcean API key and other information", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?git-credentials$`), - description: "git-credential-store helper credentials file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`config/hub$`), - description: "GitHub Hub command-line client configuration file", - comment: "Can contain GitHub API access token", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?gitconfig$`), - description: "Git configuration file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?chef/(.*)\.pem$`), - description: "Chef private key", - comment: "Can be used to authenticate against Chef servers", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`etc/shadow$`), - description: "Potential Linux shadow file", - comment: "Contains hashed passwords for system users", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`etc/passwd$`), - description: "Potential Linux passwd file", - comment: "Contains system user information", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?dockercfg$`), - description: "Docker configuration file", - comment: "Can contain credentials for public or private Docker registries", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?npmrc$`), - description: "NPM configuration file", - comment: "Can contain credentials for NPM registries", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?env$`), - description: "Environment configuration file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`credential`), - description: "Contains word: credential", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`password`), - description: "Contains word: password", - comment: "", - }, + SimpleSignature{ + part: PartExtension, + match: ".pem", + description: "Potential cryptographic private key", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".log", + description: "Log file", + comment: "Log files can contain secret HTTP endpoints, session IDs, API keys and other goodies", + }, + SimpleSignature{ + part: PartExtension, + match: ".pkcs12", + description: "Potential cryptographic key bundle", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".p12", + description: "Potential cryptographic key bundle", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".pfx", + description: "Potential cryptographic key bundle", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".asc", + description: "Potential cryptographic key bundle", + comment: "", + }, + SimpleSignature{ + part: PartFilename, + match: "otr.private_key", + description: "Pidgin OTR private key", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".ovpn", + description: "OpenVPN client configuration file", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".cscfg", + description: "Azure service configuration schema file", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".rdp", + description: "Remote Desktop connection file", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".mdf", + description: "Microsoft SQL database file", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".sdf", + description: "Microsoft SQL server compact database file", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".sqlite", + description: "SQLite database file", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".bek", + description: "Microsoft BitLocker recovery key file", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".tpm", + description: "Microsoft BitLocker Trusted Platform Module password file", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".fve", + description: "Windows BitLocker full volume encrypted data file", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".jks", + description: "Java keystore file", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".psafe3", + description: "Password Safe database file", + comment: "", + }, + SimpleSignature{ + part: PartFilename, + match: "secret_token.rb", + description: "Ruby On Rails secret token configuration file", + comment: "If the Rails secret token is known, it can allow for remote code execution (http://www.exploit-db.com/exploits/27527/)", + }, + SimpleSignature{ + part: PartFilename, + match: "carrierwave.rb", + description: "Carrierwave configuration file", + comment: "Can contain credentials for cloud storage systems such as Amazon S3 and Google Storage", + }, + SimpleSignature{ + part: PartFilename, + match: "database.yml", + description: "Potential Ruby On Rails database configuration file", + comment: "Can contain database credentials", + }, + SimpleSignature{ + part: PartFilename, + match: "omniauth.rb", + description: "OmniAuth configuration file", + comment: "The OmniAuth configuration file can contain client application secrets", + }, + SimpleSignature{ + part: PartFilename, + match: "settings.py", + description: "Django configuration file", + comment: "Can contain database credentials, cloud storage system credentials, and other secrets", + }, + SimpleSignature{ + part: PartExtension, + match: ".agilekeychain", + description: "1Password password manager database file", + comment: "Feed it to Hashcat and see if you're lucky", + }, + SimpleSignature{ + part: PartExtension, + match: ".keychain", + description: "Apple Keychain database file", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".pcap", + description: "Network traffic capture file", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".gnucash", + description: "GnuCash database file", + comment: "", + }, + SimpleSignature{ + part: PartFilename, + match: "jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin.xml", + description: "Jenkins publish over SSH plugin file", + comment: "", + }, + SimpleSignature{ + part: PartFilename, + match: "credentials.xml", + description: "Potential Jenkins credentials file", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".kwallet", + description: "KDE Wallet Manager database file", + comment: "", + }, + SimpleSignature{ + part: PartFilename, + match: "LocalSettings.php", + description: "Potential MediaWiki configuration file", + comment: "", + }, + SimpleSignature{ + part: PartExtension, + match: ".tblk", + description: "Tunnelblick VPN configuration file", + comment: "", + }, + SimpleSignature{ + part: PartFilename, + match: "Favorites.plist", + description: "Sequel Pro MySQL database manager bookmark file", + comment: "", + }, + SimpleSignature{ + part: PartFilename, + match: "configuration.user.xpl", + description: "Little Snitch firewall configuration file", + comment: "Contains traffic rules for applications", + }, + SimpleSignature{ + part: PartExtension, + match: ".dayone", + description: "Day One journal file", + comment: "Now it's getting creepy...", + }, + SimpleSignature{ + part: PartFilename, + match: "journal.txt", + description: "Potential jrnl journal file", + comment: "Now it's getting creepy...", + }, + SimpleSignature{ + part: PartFilename, + match: "knife.rb", + description: "Chef Knife configuration file", + comment: "Can contain references to Chef servers", + }, + SimpleSignature{ + part: PartFilename, + match: "proftpdpasswd", + description: "cPanel backup ProFTPd credentials file", + comment: "Contains usernames and password hashes for FTP accounts", + }, + SimpleSignature{ + part: PartFilename, + match: "robomongo.json", + description: "Robomongo MongoDB manager configuration file", + comment: "Can contain credentials for MongoDB databases", + }, + SimpleSignature{ + part: PartFilename, + match: "filezilla.xml", + description: "FileZilla FTP configuration file", + comment: "Can contain credentials for FTP servers", + }, + SimpleSignature{ + part: PartFilename, + match: "recentservers.xml", + description: "FileZilla FTP recent servers file", + comment: "Can contain credentials for FTP servers", + }, + SimpleSignature{ + part: PartFilename, + match: "ventrilo_srv.ini", + description: "Ventrilo server configuration file", + comment: "Can contain passwords", + }, + SimpleSignature{ + part: PartFilename, + match: "terraform.tfvars", + description: "Terraform variable config file", + comment: "Can contain credentials for terraform providers", + }, + SimpleSignature{ + part: PartFilename, + match: ".exports", + description: "Shell configuration file", + comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", + }, + SimpleSignature{ + part: PartFilename, + match: ".functions", + description: "Shell configuration file", + comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", + }, + SimpleSignature{ + part: PartFilename, + match: ".extra", + description: "Shell configuration file", + comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^.*_rsa$`), + description: "Private SSH key", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^.*_dsa$`), + description: "Private SSH key", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^.*_ed25519$`), + description: "Private SSH key", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^.*_ecdsa$`), + description: "Private SSH key", + comment: "", + }, + PatternSignature{ + part: PartPath, + match: regexp.MustCompile(`\.?ssh/config$`), + description: "SSH configuration file", + comment: "", + }, + PatternSignature{ + part: PartExtension, + match: regexp.MustCompile(`^key(pair)?$`), + description: "Potential cryptographic private key", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?(bash_|zsh_|sh_|z)?history$`), + description: "Shell command history file", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?mysql_history$`), + description: "MySQL client command history file", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?psql_history$`), + description: "PostgreSQL client command history file", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?pgpass$`), + description: "PostgreSQL password file", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?irb_history$`), + description: "Ruby IRB console history file", + comment: "", + }, + PatternSignature{ + part: PartPath, + match: regexp.MustCompile(`\.?purple/accounts\.xml$`), + description: "Pidgin chat client account configuration file", + comment: "", + }, + PatternSignature{ + part: PartPath, + match: regexp.MustCompile(`\.?xchat2?/servlist_?\.conf$`), + description: "Hexchat/XChat IRC client server list configuration file", + comment: "", + }, + PatternSignature{ + part: PartPath, + match: regexp.MustCompile(`\.?irssi/config$`), + description: "Irssi IRC client configuration file", + comment: "", + }, + PatternSignature{ + part: PartPath, + match: regexp.MustCompile(`\.?recon-ng/keys\.db$`), + description: "Recon-ng web reconnaissance framework API key database", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?dbeaver-data-sources.xml$`), + description: "DBeaver SQL database manager configuration file", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?muttrc$`), + description: "Mutt e-mail client configuration file", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?s3cfg$`), + description: "S3cmd configuration file", + comment: "", + }, + PatternSignature{ + part: PartPath, + match: regexp.MustCompile(`\.?aws/credentials$`), + description: "AWS CLI credentials file", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^sftp-config(\.json)?$`), + description: "SFTP connection configuration file", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?trc$`), + description: "T command-line Twitter client configuration file", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?gitrobrc$`), + description: "Well, this is awkward... Gitrob configuration file", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?(bash|zsh|csh)rc$`), + description: "Shell configuration file", + comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?(bash_|zsh_)?profile$`), + description: "Shell profile configuration file", + comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?(bash_|zsh_)?aliases$`), + description: "Shell command alias configuration file", + comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`config(\.inc)?\.php$`), + description: "PHP configuration file", + comment: "", + }, + PatternSignature{ + part: PartExtension, + match: regexp.MustCompile(`^key(store|ring)$`), + description: "GNOME Keyring database file", + comment: "", + }, + PatternSignature{ + part: PartExtension, + match: regexp.MustCompile(`^kdbx?$`), + description: "KeePass password manager database file", + comment: "Feed it to Hashcat and see if you're lucky", + }, + PatternSignature{ + part: PartExtension, + match: regexp.MustCompile(`^sql(dump)?$`), + description: "SQL dump file", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?htpasswd$`), + description: "Apache htpasswd file", + comment: "", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^(\.|_)?netrc$`), + description: "Configuration file for auto-login process", + comment: "Can contain username and password", + }, + PatternSignature{ + part: PartPath, + match: regexp.MustCompile(`\.?gem/credentials$`), + description: "Rubygems credentials file", + comment: "Can contain API key for a rubygems.org account", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?tugboat$`), + description: "Tugboat DigitalOcean management tool configuration", + comment: "", + }, + PatternSignature{ + part: PartPath, + match: regexp.MustCompile(`doctl/config.yaml$`), + description: "DigitalOcean doctl command-line client configuration file", + comment: "Contains DigitalOcean API key and other information", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?git-credentials$`), + description: "git-credential-store helper credentials file", + comment: "", + }, + PatternSignature{ + part: PartPath, + match: regexp.MustCompile(`config/hub$`), + description: "GitHub Hub command-line client configuration file", + comment: "Can contain GitHub API access token", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?gitconfig$`), + description: "Git configuration file", + comment: "", + }, + PatternSignature{ + part: PartPath, + match: regexp.MustCompile(`\.?chef/(.*)\.pem$`), + description: "Chef private key", + comment: "Can be used to authenticate against Chef servers", + }, + PatternSignature{ + part: PartPath, + match: regexp.MustCompile(`etc/shadow$`), + description: "Potential Linux shadow file", + comment: "Contains hashed passwords for system users", + }, + PatternSignature{ + part: PartPath, + match: regexp.MustCompile(`etc/passwd$`), + description: "Potential Linux passwd file", + comment: "Contains system user information", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?dockercfg$`), + description: "Docker configuration file", + comment: "Can contain credentials for public or private Docker registries", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?npmrc$`), + description: "NPM configuration file", + comment: "Can contain credentials for NPM registries", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^\.?env$`), + description: "Environment configuration file", + comment: "", + }, + PatternSignature{ + part: PartPath, + match: regexp.MustCompile(`credential`), + description: "Contains word: credential", + comment: "", + }, + PatternSignature{ + part: PartPath, + match: regexp.MustCompile(`password`), + description: "Contains word: password", + comment: "", + }, } From 7e6ec0cddeb054fe01997347539f501d4262b153 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 2 Mar 2020 13:52:30 -0500 Subject: [PATCH 012/226] formatting only --- build.sh | 11 +- core/options.go | 54 +++--- main.go | 446 ++++++++++++++++++++++++------------------------ release.sh | 5 +- 4 files changed, 259 insertions(+), 257 deletions(-) diff --git a/build.sh b/build.sh index 4fb28a58..b22e0ba0 100755 --- a/build.sh +++ b/build.sh @@ -5,7 +5,10 @@ VERSION=$(cat core/banner.go | grep Version | cut -d '"' -f 2) bin_dep() { BIN=$1 - which $BIN > /dev/null || { echo "[-] Dependency $BIN not found !"; exit 1; } + which $BIN >/dev/null || { + echo "[-] Dependency $BIN not found !" + exit 1 + } } create_exe_archive() { @@ -14,7 +17,7 @@ create_exe_archive() { OUTPUT=$1 echo "[*] Creating archive $OUTPUT ..." - zip -j "$OUTPUT" gitrob.exe ../README.md ../LICENSE.txt > /dev/null + zip -j "$OUTPUT" gitrob.exe ../README.md ../LICENSE.txt >/dev/null rm -rf gitrob gitrob.exe } @@ -24,7 +27,7 @@ create_archive() { OUTPUT=$1 echo "[*] Creating archive $OUTPUT ..." - zip -j "$OUTPUT" gitrob ../README.md ../LICENSE.md > /dev/null + zip -j "$OUTPUT" gitrob ../README.md ../LICENSE.md >/dev/null rm -rf gitrob gitrob.exe } @@ -50,7 +53,7 @@ cd $BUILD_FOLDER build_linux_amd64 && create_archive gitrob_linux_amd64_$VERSION.zip build_macos_amd64 && create_archive gitrob_macos_amd64_$VERSION.zip build_windows_amd64 && create_exe_archive gitrob_windows_amd64_$VERSION.zip -shasum -a 256 * > checksums.txt +shasum -a 256 * >checksums.txt echo echo diff --git a/core/options.go b/core/options.go index 3df33dbc..42a7d016 100644 --- a/core/options.go +++ b/core/options.go @@ -1,41 +1,41 @@ package core import ( - "flag" + "flag" ) type Options struct { - CommitDepth *int - GithubAccessToken *string `json:"-"` + CommitDepth *int + GithubAccessToken *string `json:"-"` GitLabAccessToken *string `json:"-"` - NoExpandOrgs *bool - Threads *int - Save *string `json:"-"` - Load *string `json:"-"` - BindAddress *string - Port *int - Silent *bool - Debug *bool - Logins []string + NoExpandOrgs *bool + Threads *int + Save *string `json:"-"` + Load *string `json:"-"` + BindAddress *string + Port *int + Silent *bool + Debug *bool + Logins []string } func ParseOptions() (Options, error) { - options := Options{ - CommitDepth: flag.Int("commit-depth", 500, "Number of repository commits to process"), - GithubAccessToken: flag.String("github-access-token", "", "GitHub access token to use for API requests"), + options := Options{ + CommitDepth: flag.Int("commit-depth", 500, "Number of repository commits to process"), + GithubAccessToken: flag.String("github-access-token", "", "GitHub access token to use for API requests"), GitLabAccessToken: flag.String("gitlab-access-token", "", "GitLab access token to use for API requests"), - NoExpandOrgs: flag.Bool("no-expand-orgs", false, "Don't add members to targets when processing organizations"), - Threads: flag.Int("threads", 0, "Number of concurrent threads (default number of logical CPUs)"), - Save: flag.String("save", "", "Save session to file"), - Load: flag.String("load", "", "Load session file"), - BindAddress: flag.String("bind-address", "127.0.0.1", "Address to bind web server to"), - Port: flag.Int("port", 9393, "Port to run web server on"), - Silent: flag.Bool("silent", false, "Suppress all output except for errors"), - Debug: flag.Bool("debug", false, "Print debugging information"), - } + NoExpandOrgs: flag.Bool("no-expand-orgs", false, "Don't add members to targets when processing organizations"), + Threads: flag.Int("threads", 0, "Number of concurrent threads (default number of logical CPUs)"), + Save: flag.String("save", "", "Save session to file"), + Load: flag.String("load", "", "Load session file"), + BindAddress: flag.String("bind-address", "127.0.0.1", "Address to bind web server to"), + Port: flag.Int("port", 9393, "Port to run web server on"), + Silent: flag.Bool("silent", false, "Suppress all output except for errors"), + Debug: flag.Bool("debug", false, "Print debugging information"), + } - flag.Parse() - options.Logins = flag.Args() + flag.Parse() + options.Logins = flag.Args() - return options, nil + return options, nil } diff --git a/main.go b/main.go index eefdfd95..dbf749d8 100644 --- a/main.go +++ b/main.go @@ -1,247 +1,247 @@ package main import ( - "fmt" - "os" - "strings" - "sync" - "time" + "fmt" + "os" + "strings" + "sync" + "time" "github.com/codeEmitter/gitrob/core" ) var ( - sess *core.Session - err error + sess *core.Session + err error ) func GatherTargets(sess *core.Session) { - sess.Stats.Status = core.StatusGathering - sess.Out.Important("Gathering targets...\n") - for _, login := range sess.Options.Logins { - target, err := core.GetUserOrOrganization(login, sess.GithubClient) - if err != nil { - sess.Out.Error(" Error retrieving information on %s: %s\n", login, err) - continue - } - sess.Out.Debug("%s (ID: %d) type: %s\n", *target.Login, *target.ID, *target.Type) - sess.AddTarget(target) - if *sess.Options.NoExpandOrgs == false && *target.Type == "Organization" { - sess.Out.Debug("Gathering members of %s (ID: %d)...\n", *target.Login, *target.ID) - members, err := core.GetOrganizationMembers(target.Login, sess.GithubClient) - if err != nil { - sess.Out.Error(" Error retrieving members of %s: %s\n", *target.Login, err) - continue - } - for _, member := range members { - sess.Out.Debug("Adding organization member %s (ID: %d) to targets\n", *member.Login, *member.ID) - sess.AddTarget(member) - } - } - } + sess.Stats.Status = core.StatusGathering + sess.Out.Important("Gathering targets...\n") + for _, login := range sess.Options.Logins { + target, err := core.GetUserOrOrganization(login, sess.GithubClient) + if err != nil { + sess.Out.Error(" Error retrieving information on %s: %s\n", login, err) + continue + } + sess.Out.Debug("%s (ID: %d) type: %s\n", *target.Login, *target.ID, *target.Type) + sess.AddTarget(target) + if *sess.Options.NoExpandOrgs == false && *target.Type == "Organization" { + sess.Out.Debug("Gathering members of %s (ID: %d)...\n", *target.Login, *target.ID) + members, err := core.GetOrganizationMembers(target.Login, sess.GithubClient) + if err != nil { + sess.Out.Error(" Error retrieving members of %s: %s\n", *target.Login, err) + continue + } + for _, member := range members { + sess.Out.Debug("Adding organization member %s (ID: %d) to targets\n", *member.Login, *member.ID) + sess.AddTarget(member) + } + } + } } func GatherRepositories(sess *core.Session) { - var ch = make(chan *core.GithubOwner, len(sess.Targets)) - var wg sync.WaitGroup - var threadNum int - if len(sess.Targets) == 1 { - threadNum = 1 - } else if len(sess.Targets) <= *sess.Options.Threads { - threadNum = len(sess.Targets) - 1 - } else { - threadNum = *sess.Options.Threads - } - wg.Add(threadNum) - sess.Out.Debug("Threads for repository gathering: %d\n", threadNum) - for i := 0; i < threadNum; i++ { - go func() { - for { - target, ok := <-ch - if !ok { - wg.Done() - return - } - repos, err := core.GetRepositoriesFromOwner(target.Login, sess.GithubClient) - if err != nil { - sess.Out.Error(" Failed to retrieve repositories from %s: %s\n", *target.Login, err) - } - if len(repos) == 0 { - continue - } - for _, repo := range repos { - sess.Out.Debug(" Retrieved repository: %s\n", *repo.FullName) - sess.AddRepository(repo) - } - sess.Stats.IncrementTargets() - sess.Out.Info(" Retrieved %d %s from %s\n", len(repos), core.Pluralize(len(repos), "repository", "repositories"), *target.Login) - } - }() - } - - for _, target := range sess.Targets { - ch <- target - } - close(ch) - wg.Wait() + var ch = make(chan *core.GithubOwner, len(sess.Targets)) + var wg sync.WaitGroup + var threadNum int + if len(sess.Targets) == 1 { + threadNum = 1 + } else if len(sess.Targets) <= *sess.Options.Threads { + threadNum = len(sess.Targets) - 1 + } else { + threadNum = *sess.Options.Threads + } + wg.Add(threadNum) + sess.Out.Debug("Threads for repository gathering: %d\n", threadNum) + for i := 0; i < threadNum; i++ { + go func() { + for { + target, ok := <-ch + if !ok { + wg.Done() + return + } + repos, err := core.GetRepositoriesFromOwner(target.Login, sess.GithubClient) + if err != nil { + sess.Out.Error(" Failed to retrieve repositories from %s: %s\n", *target.Login, err) + } + if len(repos) == 0 { + continue + } + for _, repo := range repos { + sess.Out.Debug(" Retrieved repository: %s\n", *repo.FullName) + sess.AddRepository(repo) + } + sess.Stats.IncrementTargets() + sess.Out.Info(" Retrieved %d %s from %s\n", len(repos), core.Pluralize(len(repos), "repository", "repositories"), *target.Login) + } + }() + } + + for _, target := range sess.Targets { + ch <- target + } + close(ch) + wg.Wait() } func AnalyzeRepositories(sess *core.Session) { - sess.Stats.Status = core.StatusAnalyzing - var ch = make(chan *core.GithubRepository, len(sess.Repositories)) - var wg sync.WaitGroup - var threadNum int - if len(sess.Repositories) <= 1 { - threadNum = 1 - } else if len(sess.Repositories) <= *sess.Options.Threads { - threadNum = len(sess.Repositories) - 1 - } else { - threadNum = *sess.Options.Threads - } - wg.Add(threadNum) - sess.Out.Debug("Threads for repository analysis: %d\n", threadNum) - - sess.Out.Important("Analyzing %d %s...\n", len(sess.Repositories), core.Pluralize(len(sess.Repositories), "repository", "repositories")) - - for i := 0; i < threadNum; i++ { - go func(tid int) { - for { - sess.Out.Debug("[THREAD #%d] Requesting new repository to analyze...\n", tid) - repo, ok := <-ch - if !ok { - sess.Out.Debug("[THREAD #%d] No more tasks, marking WaitGroup as done\n", tid) - wg.Done() - return - } - - sess.Out.Debug("[THREAD #%d][%s] Cloning repository...\n", tid, *repo.FullName) - clone, path, err := core.CloneRepository(repo.CloneURL, repo.DefaultBranch, *sess.Options.CommitDepth) - if err != nil { - if err.Error() != "remote repository is empty" { - sess.Out.Error("Error cloning repository %s: %s\n", *repo.FullName, err) - } - sess.Stats.IncrementRepositories() - sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) - continue - } - sess.Out.Debug("[THREAD #%d][%s] Cloned repository to: %s\n", tid, *repo.FullName, path) - - history, err := core.GetRepositoryHistory(clone) - if err != nil { - sess.Out.Error("[THREAD #%d][%s] Error getting commit history: %s\n", tid, *repo.FullName, err) - os.RemoveAll(path) - sess.Stats.IncrementRepositories() - sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) - continue - } - sess.Out.Debug("[THREAD #%d][%s] Number of commits: %d\n", tid, *repo.FullName, len(history)) - - for _, commit := range history { - sess.Out.Debug("[THREAD #%d][%s] Analyzing commit: %s\n", tid, *repo.FullName, commit.Hash) - changes, _ := core.GetChanges(commit, clone) - sess.Out.Debug("[THREAD #%d][%s] Changes in %s: %d\n", tid, *repo.FullName, commit.Hash, len(changes)) - for _, change := range changes { - changeAction := core.GetChangeAction(change) - path := core.GetChangePath(change) - matchFile := core.NewMatchFile(path) - if matchFile.IsSkippable() { - sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.FullName, matchFile.Path) - continue - } - sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.FullName, matchFile.Path) - for _, signature := range core.Signatures { - if signature.Match(matchFile) { - - finding := &core.Finding{ - FilePath: path, - Action: changeAction, - Description: signature.Description(), - Comment: signature.Comment(), - RepositoryOwner: *repo.Owner, - RepositoryName: *repo.Name, - CommitHash: commit.Hash.String(), - CommitMessage: strings.TrimSpace(commit.Message), - CommitAuthor: commit.Author.String(), - } - finding.Initialize() - sess.AddFinding(finding) - - sess.Out.Warn(" %s: %s\n", strings.ToUpper(changeAction), finding.Description) - sess.Out.Info(" Path.......: %s\n", finding.FilePath) - sess.Out.Info(" Repo.......: %s\n", *repo.FullName) - sess.Out.Info(" Message....: %s\n", core.TruncateString(finding.CommitMessage, 100)) - sess.Out.Info(" Author.....: %s\n", finding.CommitAuthor) - if finding.Comment != "" { - sess.Out.Info(" Comment....: %s\n", finding.Comment) - } - sess.Out.Info(" File URL...: %s\n", finding.FileUrl) - sess.Out.Info(" Commit URL.: %s\n", finding.CommitUrl) - sess.Out.Info(" ------------------------------------------------\n\n") - sess.Stats.IncrementFindings() - break - } - } - sess.Stats.IncrementFiles() - } - sess.Stats.IncrementCommits() - sess.Out.Debug("[THREAD #%d][%s] Done analyzing changes in %s\n", tid, *repo.FullName, commit.Hash) - } - sess.Out.Debug("[THREAD #%d][%s] Done analyzing commits\n", tid, *repo.FullName) - os.RemoveAll(path) - sess.Out.Debug("[THREAD #%d][%s] Deleted %s\n", tid, *repo.FullName, path) - sess.Stats.IncrementRepositories() - sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) - } - }(i) - } - for _, repo := range sess.Repositories { - ch <- repo - } - close(ch) - wg.Wait() + sess.Stats.Status = core.StatusAnalyzing + var ch = make(chan *core.GithubRepository, len(sess.Repositories)) + var wg sync.WaitGroup + var threadNum int + if len(sess.Repositories) <= 1 { + threadNum = 1 + } else if len(sess.Repositories) <= *sess.Options.Threads { + threadNum = len(sess.Repositories) - 1 + } else { + threadNum = *sess.Options.Threads + } + wg.Add(threadNum) + sess.Out.Debug("Threads for repository analysis: %d\n", threadNum) + + sess.Out.Important("Analyzing %d %s...\n", len(sess.Repositories), core.Pluralize(len(sess.Repositories), "repository", "repositories")) + + for i := 0; i < threadNum; i++ { + go func(tid int) { + for { + sess.Out.Debug("[THREAD #%d] Requesting new repository to analyze...\n", tid) + repo, ok := <-ch + if !ok { + sess.Out.Debug("[THREAD #%d] No more tasks, marking WaitGroup as done\n", tid) + wg.Done() + return + } + + sess.Out.Debug("[THREAD #%d][%s] Cloning repository...\n", tid, *repo.FullName) + clone, path, err := core.CloneRepository(repo.CloneURL, repo.DefaultBranch, *sess.Options.CommitDepth) + if err != nil { + if err.Error() != "remote repository is empty" { + sess.Out.Error("Error cloning repository %s: %s\n", *repo.FullName, err) + } + sess.Stats.IncrementRepositories() + sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) + continue + } + sess.Out.Debug("[THREAD #%d][%s] Cloned repository to: %s\n", tid, *repo.FullName, path) + + history, err := core.GetRepositoryHistory(clone) + if err != nil { + sess.Out.Error("[THREAD #%d][%s] Error getting commit history: %s\n", tid, *repo.FullName, err) + os.RemoveAll(path) + sess.Stats.IncrementRepositories() + sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) + continue + } + sess.Out.Debug("[THREAD #%d][%s] Number of commits: %d\n", tid, *repo.FullName, len(history)) + + for _, commit := range history { + sess.Out.Debug("[THREAD #%d][%s] Analyzing commit: %s\n", tid, *repo.FullName, commit.Hash) + changes, _ := core.GetChanges(commit, clone) + sess.Out.Debug("[THREAD #%d][%s] Changes in %s: %d\n", tid, *repo.FullName, commit.Hash, len(changes)) + for _, change := range changes { + changeAction := core.GetChangeAction(change) + path := core.GetChangePath(change) + matchFile := core.NewMatchFile(path) + if matchFile.IsSkippable() { + sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.FullName, matchFile.Path) + continue + } + sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.FullName, matchFile.Path) + for _, signature := range core.Signatures { + if signature.Match(matchFile) { + + finding := &core.Finding{ + FilePath: path, + Action: changeAction, + Description: signature.Description(), + Comment: signature.Comment(), + RepositoryOwner: *repo.Owner, + RepositoryName: *repo.Name, + CommitHash: commit.Hash.String(), + CommitMessage: strings.TrimSpace(commit.Message), + CommitAuthor: commit.Author.String(), + } + finding.Initialize() + sess.AddFinding(finding) + + sess.Out.Warn(" %s: %s\n", strings.ToUpper(changeAction), finding.Description) + sess.Out.Info(" Path.......: %s\n", finding.FilePath) + sess.Out.Info(" Repo.......: %s\n", *repo.FullName) + sess.Out.Info(" Message....: %s\n", core.TruncateString(finding.CommitMessage, 100)) + sess.Out.Info(" Author.....: %s\n", finding.CommitAuthor) + if finding.Comment != "" { + sess.Out.Info(" Comment....: %s\n", finding.Comment) + } + sess.Out.Info(" File URL...: %s\n", finding.FileUrl) + sess.Out.Info(" Commit URL.: %s\n", finding.CommitUrl) + sess.Out.Info(" ------------------------------------------------\n\n") + sess.Stats.IncrementFindings() + break + } + } + sess.Stats.IncrementFiles() + } + sess.Stats.IncrementCommits() + sess.Out.Debug("[THREAD #%d][%s] Done analyzing changes in %s\n", tid, *repo.FullName, commit.Hash) + } + sess.Out.Debug("[THREAD #%d][%s] Done analyzing commits\n", tid, *repo.FullName) + os.RemoveAll(path) + sess.Out.Debug("[THREAD #%d][%s] Deleted %s\n", tid, *repo.FullName, path) + sess.Stats.IncrementRepositories() + sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) + } + }(i) + } + for _, repo := range sess.Repositories { + ch <- repo + } + close(ch) + wg.Wait() } func PrintSessionStats(sess *core.Session) { - sess.Out.Info("\nFindings....: %d\n", sess.Stats.Findings) - sess.Out.Info("Files.......: %d\n", sess.Stats.Files) - sess.Out.Info("Commits.....: %d\n", sess.Stats.Commits) - sess.Out.Info("Repositories: %d\n", sess.Stats.Repositories) - sess.Out.Info("Targets.....: %d\n\n", sess.Stats.Targets) + sess.Out.Info("\nFindings....: %d\n", sess.Stats.Findings) + sess.Out.Info("Files.......: %d\n", sess.Stats.Files) + sess.Out.Info("Commits.....: %d\n", sess.Stats.Commits) + sess.Out.Info("Repositories: %d\n", sess.Stats.Repositories) + sess.Out.Info("Targets.....: %d\n\n", sess.Stats.Targets) } func main() { - if sess, err = core.NewSession(); err != nil { - fmt.Println(err) - os.Exit(1) - } - - sess.Out.Info("%s\n\n", core.ASCIIBanner) - sess.Out.Important("%s v%s started at %s\n", core.Name, core.Version, sess.Stats.StartedAt.Format(time.RFC3339)) - sess.Out.Important("Loaded %d signatures\n", len(core.Signatures)) - sess.Out.Important("Web interface available at http://%s:%d\n", *sess.Options.BindAddress, *sess.Options.Port) - - if sess.Stats.Status == "finished" { - sess.Out.Important("Loaded session file: %s\n", *sess.Options.Load) - } else { - if len(sess.Options.Logins) == 0 { - sess.Out.Fatal("Please provide at least one GitHub organization or user\n") - } - - GatherTargets(sess) - GatherRepositories(sess) - AnalyzeRepositories(sess) - sess.Finish() - - if *sess.Options.Save != "" { - err := sess.SaveToFile(*sess.Options.Save) - if err != nil { - sess.Out.Error("Error saving session to %s: %s\n", *sess.Options.Save, err) - } - sess.Out.Important("Saved session to: %s\n\n", *sess.Options.Save) - } - } - - PrintSessionStats(sess) - sess.Out.Important("Press Ctrl+C to stop web server and exit.\n\n") - select {} + if sess, err = core.NewSession(); err != nil { + fmt.Println(err) + os.Exit(1) + } + + sess.Out.Info("%s\n\n", core.ASCIIBanner) + sess.Out.Important("%s v%s started at %s\n", core.Name, core.Version, sess.Stats.StartedAt.Format(time.RFC3339)) + sess.Out.Important("Loaded %d signatures\n", len(core.Signatures)) + sess.Out.Important("Web interface available at http://%s:%d\n", *sess.Options.BindAddress, *sess.Options.Port) + + if sess.Stats.Status == "finished" { + sess.Out.Important("Loaded session file: %s\n", *sess.Options.Load) + } else { + if len(sess.Options.Logins) == 0 { + sess.Out.Fatal("Please provide at least one GitHub organization or user\n") + } + + GatherTargets(sess) + GatherRepositories(sess) + AnalyzeRepositories(sess) + sess.Finish() + + if *sess.Options.Save != "" { + err := sess.SaveToFile(*sess.Options.Save) + if err != nil { + sess.Out.Error("Error saving session to %s: %s\n", *sess.Options.Save, err) + } + sess.Out.Important("Saved session to: %s\n\n", *sess.Options.Save) + } + } + + PrintSessionStats(sess) + sess.Out.Important("Press Ctrl+C to stop web server and exit.\n\n") + select {} } diff --git a/release.sh b/release.sh index c9795f00..00119273 100755 --- a/release.sh +++ b/release.sh @@ -2,7 +2,7 @@ CURRENT_VERSION=$(cat core/banner.go | grep Version | cut -d '"' -f 2) TO_UPDATE=( - core/banner.go + core/banner.go ) read -p "[?] Did you remember to update CHANGELOG.md? " @@ -13,8 +13,7 @@ read NEW_VERSION echo "[*] Pushing and tagging version $NEW_VERSION in 5 seconds..." sleep 5 -for file in "${TO_UPDATE[@]}" -do +for file in "${TO_UPDATE[@]}"; do echo "[*] Patching $file ..." sed -i "s/$CURRENT_VERSION/$NEW_VERSION/g" $file git add $file From 4d71b7b960546cd9748415e955ac093029d57701 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 2 Mar 2020 13:59:18 -0500 Subject: [PATCH 013/226] Error text correction --- core/session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/session.go b/core/session.go index 927fa0f8..1235b50d 100644 --- a/core/session.go +++ b/core/session.go @@ -131,7 +131,7 @@ func (s *Session) InitAccessToken() { s.GitLabAccessToken = *s.Options.GitLabAccessToken } if s.GitLabAccessToken != "" && s.GithubAccessToken != "" { - s.Out.Fatal("Both a GitLab and Github token are present. Only one may set.") + s.Out.Fatal("Both a GitLab and Github token are present. Only one may be set.") } if s.GitLabAccessToken == "" && s.GithubAccessToken == "" { s.Out.Fatal("No valid API token was found.") From dc5b9aed79242b26f668fdec7827288b6575aecd Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 2 Mar 2020 15:06:19 -0500 Subject: [PATCH 014/226] break out a validation step --- core/session.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/session.go b/core/session.go index 1235b50d..3014c0a3 100644 --- a/core/session.go +++ b/core/session.go @@ -60,7 +60,8 @@ func (s *Session) Start() { s.InitLogger() s.InitThreads() s.InitAccessToken() - s.InitGithubClient() + s.ValidateTokenConfig() + s.InitAPIClient() s.InitRouter() } @@ -130,6 +131,9 @@ func (s *Session) InitAccessToken() { } else { s.GitLabAccessToken = *s.Options.GitLabAccessToken } +} + +func (s *Session) ValidateTokenConfig() { if s.GitLabAccessToken != "" && s.GithubAccessToken != "" { s.Out.Fatal("Both a GitLab and Github token are present. Only one may be set.") } From 9e4ce901d6fc252e0e92ca30384442db224d1da6 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 2 Mar 2020 15:06:41 -0500 Subject: [PATCH 015/226] setup a github client if a github token is present --- core/session.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/core/session.go b/core/session.go index 3014c0a3..3c915575 100644 --- a/core/session.go +++ b/core/session.go @@ -142,14 +142,16 @@ func (s *Session) ValidateTokenConfig() { } } -func (s *Session) InitGithubClient() { - ctx := context.Background() - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: s.GithubAccessToken}, - ) - tc := oauth2.NewClient(ctx, ts) - s.GithubClient = github.NewClient(tc) - s.GithubClient.UserAgent = fmt.Sprintf("%s v%s", Name, Version) +func (s *Session) InitAPIClient() { + if s.GithubAccessToken != "" { + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: s.GithubAccessToken}, + ) + tc := oauth2.NewClient(ctx, ts) + s.GithubClient = github.NewClient(tc) + s.GithubClient.UserAgent = fmt.Sprintf("%s v%s", Name, Version) + } } func (s *Session) InitThreads() { From 14bd1b704240b10a3e31a7bd6bc03aab3f72bc13 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 2 Mar 2020 15:32:28 -0500 Subject: [PATCH 016/226] initialize a gitlab client if a token is present --- core/session.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/session.go b/core/session.go index 3c915575..b1a0a499 100644 --- a/core/session.go +++ b/core/session.go @@ -13,6 +13,7 @@ import ( "github.com/gin-gonic/gin" "github.com/google/go-github/github" + "github.com/xanzy/go-gitlab" "golang.org/x/oauth2" ) @@ -49,7 +50,8 @@ type Session struct { GithubAccessToken string `json:"-"` GitLabAccessToken string `json:"-"` GithubClient *github.Client `json:"-"` - Router *gin.Engine `json:"-"` + GitLabClient *gitlab.Client + Router *gin.Engine `json:"-"` Targets []*GithubOwner Repositories []*GithubRepository Findings []*Finding @@ -152,6 +154,9 @@ func (s *Session) InitAPIClient() { s.GithubClient = github.NewClient(tc) s.GithubClient.UserAgent = fmt.Sprintf("%s v%s", Name, Version) } + if s.GitLabAccessToken != "" { + s.GitLabClient = gitlab.NewClient(nil, s.GitLabAccessToken) + } } func (s *Session) InitThreads() { From db9ebe387abc8bb47aa4352da17615a75c5c5838 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 2 Mar 2020 16:44:09 -0500 Subject: [PATCH 017/226] subpackage github specifics --- core/session.go | 9 +++++---- main.go | 11 ++++++----- {core => scm}/github.go | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) rename {core => scm}/github.go (99%) diff --git a/core/session.go b/core/session.go index b1a0a499..68146df1 100644 --- a/core/session.go +++ b/core/session.go @@ -11,6 +11,7 @@ import ( "sync" "time" + "github.com/codeEmitter/gitrob/scm" "github.com/gin-gonic/gin" "github.com/google/go-github/github" "github.com/xanzy/go-gitlab" @@ -52,8 +53,8 @@ type Session struct { GithubClient *github.Client `json:"-"` GitLabClient *gitlab.Client Router *gin.Engine `json:"-"` - Targets []*GithubOwner - Repositories []*GithubRepository + Targets []*scm.GithubOwner + Repositories []*scm.GithubRepository Findings []*Finding } @@ -72,7 +73,7 @@ func (s *Session) Finish() { s.Stats.Status = StatusFinished } -func (s *Session) AddTarget(target *GithubOwner) { +func (s *Session) AddTarget(target *scm.GithubOwner) { s.Lock() defer s.Unlock() for _, t := range s.Targets { @@ -83,7 +84,7 @@ func (s *Session) AddTarget(target *GithubOwner) { s.Targets = append(s.Targets, target) } -func (s *Session) AddRepository(repository *GithubRepository) { +func (s *Session) AddRepository(repository *scm.GithubRepository) { s.Lock() defer s.Unlock() for _, r := range s.Repositories { diff --git a/main.go b/main.go index dbf749d8..6982248b 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "time" "github.com/codeEmitter/gitrob/core" + "github.com/codeEmitter/gitrob/scm" ) var ( @@ -19,7 +20,7 @@ func GatherTargets(sess *core.Session) { sess.Stats.Status = core.StatusGathering sess.Out.Important("Gathering targets...\n") for _, login := range sess.Options.Logins { - target, err := core.GetUserOrOrganization(login, sess.GithubClient) + target, err := scm.GetUserOrOrganization(login, sess.GithubClient) if err != nil { sess.Out.Error(" Error retrieving information on %s: %s\n", login, err) continue @@ -28,7 +29,7 @@ func GatherTargets(sess *core.Session) { sess.AddTarget(target) if *sess.Options.NoExpandOrgs == false && *target.Type == "Organization" { sess.Out.Debug("Gathering members of %s (ID: %d)...\n", *target.Login, *target.ID) - members, err := core.GetOrganizationMembers(target.Login, sess.GithubClient) + members, err := scm.GetOrganizationMembers(target.Login, sess.GithubClient) if err != nil { sess.Out.Error(" Error retrieving members of %s: %s\n", *target.Login, err) continue @@ -42,7 +43,7 @@ func GatherTargets(sess *core.Session) { } func GatherRepositories(sess *core.Session) { - var ch = make(chan *core.GithubOwner, len(sess.Targets)) + var ch = make(chan *scm.GithubOwner, len(sess.Targets)) var wg sync.WaitGroup var threadNum int if len(sess.Targets) == 1 { @@ -62,7 +63,7 @@ func GatherRepositories(sess *core.Session) { wg.Done() return } - repos, err := core.GetRepositoriesFromOwner(target.Login, sess.GithubClient) + repos, err := scm.GetRepositoriesFromOwner(target.Login, sess.GithubClient) if err != nil { sess.Out.Error(" Failed to retrieve repositories from %s: %s\n", *target.Login, err) } @@ -88,7 +89,7 @@ func GatherRepositories(sess *core.Session) { func AnalyzeRepositories(sess *core.Session) { sess.Stats.Status = core.StatusAnalyzing - var ch = make(chan *core.GithubRepository, len(sess.Repositories)) + var ch = make(chan *scm.GithubRepository, len(sess.Repositories)) var wg sync.WaitGroup var threadNum int if len(sess.Repositories) <= 1 { diff --git a/core/github.go b/scm/github.go similarity index 99% rename from core/github.go rename to scm/github.go index d4db6689..341ecceb 100644 --- a/core/github.go +++ b/scm/github.go @@ -1,4 +1,4 @@ -package core +package scm import ( "context" From 5c268c7086e4d736bf651cfc366108ad05f6f0ba Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 2 Mar 2020 16:58:13 -0500 Subject: [PATCH 018/226] factor out common scm components --- core/session.go | 8 ++++---- main.go | 4 ++-- scm/default.go | 27 +++++++++++++++++++++++++++ scm/github.go | 42 ++++++++---------------------------------- 4 files changed, 41 insertions(+), 40 deletions(-) create mode 100644 scm/default.go diff --git a/core/session.go b/core/session.go index 68146df1..9479060e 100644 --- a/core/session.go +++ b/core/session.go @@ -53,8 +53,8 @@ type Session struct { GithubClient *github.Client `json:"-"` GitLabClient *gitlab.Client Router *gin.Engine `json:"-"` - Targets []*scm.GithubOwner - Repositories []*scm.GithubRepository + Targets []*scm.Owner + Repositories []*scm.Repository Findings []*Finding } @@ -73,7 +73,7 @@ func (s *Session) Finish() { s.Stats.Status = StatusFinished } -func (s *Session) AddTarget(target *scm.GithubOwner) { +func (s *Session) AddTarget(target *scm.Owner) { s.Lock() defer s.Unlock() for _, t := range s.Targets { @@ -84,7 +84,7 @@ func (s *Session) AddTarget(target *scm.GithubOwner) { s.Targets = append(s.Targets, target) } -func (s *Session) AddRepository(repository *scm.GithubRepository) { +func (s *Session) AddRepository(repository *scm.Repository) { s.Lock() defer s.Unlock() for _, r := range s.Repositories { diff --git a/main.go b/main.go index 6982248b..ba48dd29 100644 --- a/main.go +++ b/main.go @@ -43,7 +43,7 @@ func GatherTargets(sess *core.Session) { } func GatherRepositories(sess *core.Session) { - var ch = make(chan *scm.GithubOwner, len(sess.Targets)) + var ch = make(chan *scm.Owner, len(sess.Targets)) var wg sync.WaitGroup var threadNum int if len(sess.Targets) == 1 { @@ -89,7 +89,7 @@ func GatherRepositories(sess *core.Session) { func AnalyzeRepositories(sess *core.Session) { sess.Stats.Status = core.StatusAnalyzing - var ch = make(chan *scm.GithubRepository, len(sess.Repositories)) + var ch = make(chan *scm.Repository, len(sess.Repositories)) var wg sync.WaitGroup var threadNum int if len(sess.Repositories) <= 1 { diff --git a/scm/default.go b/scm/default.go new file mode 100644 index 00000000..bfd995e5 --- /dev/null +++ b/scm/default.go @@ -0,0 +1,27 @@ +package scm + +type Owner struct { + Login *string + ID *int64 + Type *string + Name *string + AvatarURL *string + URL *string + Company *string + Blog *string + Location *string + Email *string + Bio *string +} + +type Repository struct { + Owner *string + ID *int64 + Name *string + FullName *string + CloneURL *string + URL *string + DefaultBranch *string + Description *string + Homepage *string +} diff --git a/scm/github.go b/scm/github.go index 341ecceb..34b5c9a4 100644 --- a/scm/github.go +++ b/scm/github.go @@ -6,39 +6,13 @@ import ( "github.com/google/go-github/github" ) -type GithubOwner struct { - Login *string - ID *int64 - Type *string - Name *string - AvatarURL *string - URL *string - Company *string - Blog *string - Location *string - Email *string - Bio *string -} - -type GithubRepository struct { - Owner *string - ID *int64 - Name *string - FullName *string - CloneURL *string - URL *string - DefaultBranch *string - Description *string - Homepage *string -} - -func GetUserOrOrganization(login string, client *github.Client) (*GithubOwner, error) { +func GetUserOrOrganization(login string, client *github.Client) (*Owner, error) { ctx := context.Background() user, _, err := client.Users.Get(ctx, login) if err != nil { return nil, err } - return &GithubOwner{ + return &Owner{ Login: user.Login, ID: user.ID, Type: user.Type, @@ -53,8 +27,8 @@ func GetUserOrOrganization(login string, client *github.Client) (*GithubOwner, e }, nil } -func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*GithubRepository, error) { - var allRepos []*GithubRepository +func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*Repository, error) { + var allRepos []*Repository loginVal := *login ctx := context.Background() opt := &github.RepositoryListOptions{ @@ -68,7 +42,7 @@ func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*GithubRe } for _, repo := range repos { if !*repo.Fork { - r := GithubRepository{ + r := Repository{ Owner: repo.Owner.Login, ID: repo.ID, Name: repo.Name, @@ -91,8 +65,8 @@ func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*GithubRe return allRepos, nil } -func GetOrganizationMembers(login *string, client *github.Client) ([]*GithubOwner, error) { - var allMembers []*GithubOwner +func GetOrganizationMembers(login *string, client *github.Client) ([]*Owner, error) { + var allMembers []*Owner loginVal := *login ctx := context.Background() opt := &github.ListMembersOptions{} @@ -102,7 +76,7 @@ func GetOrganizationMembers(login *string, client *github.Client) ([]*GithubOwne return allMembers, err } for _, member := range members { - allMembers = append(allMembers, &GithubOwner{Login: member.Login, ID: member.ID, Type: member.Type}) + allMembers = append(allMembers, &Owner{Login: member.Login, ID: member.ID, Type: member.Type}) } if resp.NextPage == 0 { break From 69a0d8a81d9339ce73ba430d4eb2f9233422abc4 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 2 Mar 2020 17:11:43 -0500 Subject: [PATCH 019/226] names mean things --- scm/{default.go => common.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scm/{default.go => common.go} (100%) diff --git a/scm/default.go b/scm/common.go similarity index 100% rename from scm/default.go rename to scm/common.go From 705ebb54ddf17b974c64793998a4a75d5eaf0ff6 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 2 Mar 2020 17:34:53 -0500 Subject: [PATCH 020/226] factor out a package for github specific things --- scm/github.go => github/default.go | 19 ++++++++++--------- main.go | 7 ++++--- 2 files changed, 14 insertions(+), 12 deletions(-) rename scm/github.go => github/default.go (83%) diff --git a/scm/github.go b/github/default.go similarity index 83% rename from scm/github.go rename to github/default.go index 34b5c9a4..126778a6 100644 --- a/scm/github.go +++ b/github/default.go @@ -1,18 +1,19 @@ -package scm +package github import ( "context" + "github.com/codeEmitter/gitrob/scm" "github.com/google/go-github/github" ) -func GetUserOrOrganization(login string, client *github.Client) (*Owner, error) { +func GetUserOrOrganization(login string, client *github.Client) (*scm.Owner, error) { ctx := context.Background() user, _, err := client.Users.Get(ctx, login) if err != nil { return nil, err } - return &Owner{ + return &scm.Owner{ Login: user.Login, ID: user.ID, Type: user.Type, @@ -27,8 +28,8 @@ func GetUserOrOrganization(login string, client *github.Client) (*Owner, error) }, nil } -func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*Repository, error) { - var allRepos []*Repository +func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*scm.Repository, error) { + var allRepos []*scm.Repository loginVal := *login ctx := context.Background() opt := &github.RepositoryListOptions{ @@ -42,7 +43,7 @@ func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*Reposito } for _, repo := range repos { if !*repo.Fork { - r := Repository{ + r := scm.Repository{ Owner: repo.Owner.Login, ID: repo.ID, Name: repo.Name, @@ -65,8 +66,8 @@ func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*Reposito return allRepos, nil } -func GetOrganizationMembers(login *string, client *github.Client) ([]*Owner, error) { - var allMembers []*Owner +func GetOrganizationMembers(login *string, client *github.Client) ([]*scm.Owner, error) { + var allMembers []*scm.Owner loginVal := *login ctx := context.Background() opt := &github.ListMembersOptions{} @@ -76,7 +77,7 @@ func GetOrganizationMembers(login *string, client *github.Client) ([]*Owner, err return allMembers, err } for _, member := range members { - allMembers = append(allMembers, &Owner{Login: member.Login, ID: member.ID, Type: member.Type}) + allMembers = append(allMembers, &scm.Owner{Login: member.Login, ID: member.ID, Type: member.Type}) } if resp.NextPage == 0 { break diff --git a/main.go b/main.go index ba48dd29..5c5671d3 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "time" "github.com/codeEmitter/gitrob/core" + "github.com/codeEmitter/gitrob/github" "github.com/codeEmitter/gitrob/scm" ) @@ -20,7 +21,7 @@ func GatherTargets(sess *core.Session) { sess.Stats.Status = core.StatusGathering sess.Out.Important("Gathering targets...\n") for _, login := range sess.Options.Logins { - target, err := scm.GetUserOrOrganization(login, sess.GithubClient) + target, err := github.GetUserOrOrganization(login, sess.GithubClient) if err != nil { sess.Out.Error(" Error retrieving information on %s: %s\n", login, err) continue @@ -29,7 +30,7 @@ func GatherTargets(sess *core.Session) { sess.AddTarget(target) if *sess.Options.NoExpandOrgs == false && *target.Type == "Organization" { sess.Out.Debug("Gathering members of %s (ID: %d)...\n", *target.Login, *target.ID) - members, err := scm.GetOrganizationMembers(target.Login, sess.GithubClient) + members, err := github.GetOrganizationMembers(target.Login, sess.GithubClient) if err != nil { sess.Out.Error(" Error retrieving members of %s: %s\n", *target.Login, err) continue @@ -63,7 +64,7 @@ func GatherRepositories(sess *core.Session) { wg.Done() return } - repos, err := scm.GetRepositoriesFromOwner(target.Login, sess.GithubClient) + repos, err := github.GetRepositoriesFromOwner(target.Login, sess.GithubClient) if err != nil { sess.Out.Error(" Failed to retrieve repositories from %s: %s\n", *target.Login, err) } From 2f0aa967dd2685d5c85748907a7932bbc830c464 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 2 Mar 2020 17:42:52 -0500 Subject: [PATCH 021/226] define common types --- scm/common.go => common/sourcecontrol.go | 2 +- core/session.go | 10 +++++----- github/default.go | 18 +++++++++--------- main.go | 6 +++--- 4 files changed, 18 insertions(+), 18 deletions(-) rename scm/common.go => common/sourcecontrol.go (96%) diff --git a/scm/common.go b/common/sourcecontrol.go similarity index 96% rename from scm/common.go rename to common/sourcecontrol.go index bfd995e5..9010bf8c 100644 --- a/scm/common.go +++ b/common/sourcecontrol.go @@ -1,4 +1,4 @@ -package scm +package common type Owner struct { Login *string diff --git a/core/session.go b/core/session.go index 9479060e..22498574 100644 --- a/core/session.go +++ b/core/session.go @@ -11,7 +11,7 @@ import ( "sync" "time" - "github.com/codeEmitter/gitrob/scm" + "github.com/codeEmitter/gitrob/common" "github.com/gin-gonic/gin" "github.com/google/go-github/github" "github.com/xanzy/go-gitlab" @@ -53,8 +53,8 @@ type Session struct { GithubClient *github.Client `json:"-"` GitLabClient *gitlab.Client Router *gin.Engine `json:"-"` - Targets []*scm.Owner - Repositories []*scm.Repository + Targets []*common.Owner + Repositories []*common.Repository Findings []*Finding } @@ -73,7 +73,7 @@ func (s *Session) Finish() { s.Stats.Status = StatusFinished } -func (s *Session) AddTarget(target *scm.Owner) { +func (s *Session) AddTarget(target *common.Owner) { s.Lock() defer s.Unlock() for _, t := range s.Targets { @@ -84,7 +84,7 @@ func (s *Session) AddTarget(target *scm.Owner) { s.Targets = append(s.Targets, target) } -func (s *Session) AddRepository(repository *scm.Repository) { +func (s *Session) AddRepository(repository *common.Repository) { s.Lock() defer s.Unlock() for _, r := range s.Repositories { diff --git a/github/default.go b/github/default.go index 126778a6..e24b37f9 100644 --- a/github/default.go +++ b/github/default.go @@ -3,17 +3,17 @@ package github import ( "context" - "github.com/codeEmitter/gitrob/scm" + "github.com/codeEmitter/gitrob/common" "github.com/google/go-github/github" ) -func GetUserOrOrganization(login string, client *github.Client) (*scm.Owner, error) { +func GetUserOrOrganization(login string, client *github.Client) (*common.Owner, error) { ctx := context.Background() user, _, err := client.Users.Get(ctx, login) if err != nil { return nil, err } - return &scm.Owner{ + return &common.Owner{ Login: user.Login, ID: user.ID, Type: user.Type, @@ -28,8 +28,8 @@ func GetUserOrOrganization(login string, client *github.Client) (*scm.Owner, err }, nil } -func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*scm.Repository, error) { - var allRepos []*scm.Repository +func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*common.Repository, error) { + var allRepos []*common.Repository loginVal := *login ctx := context.Background() opt := &github.RepositoryListOptions{ @@ -43,7 +43,7 @@ func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*scm.Repo } for _, repo := range repos { if !*repo.Fork { - r := scm.Repository{ + r := common.Repository{ Owner: repo.Owner.Login, ID: repo.ID, Name: repo.Name, @@ -66,8 +66,8 @@ func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*scm.Repo return allRepos, nil } -func GetOrganizationMembers(login *string, client *github.Client) ([]*scm.Owner, error) { - var allMembers []*scm.Owner +func GetOrganizationMembers(login *string, client *github.Client) ([]*common.Owner, error) { + var allMembers []*common.Owner loginVal := *login ctx := context.Background() opt := &github.ListMembersOptions{} @@ -77,7 +77,7 @@ func GetOrganizationMembers(login *string, client *github.Client) ([]*scm.Owner, return allMembers, err } for _, member := range members { - allMembers = append(allMembers, &scm.Owner{Login: member.Login, ID: member.ID, Type: member.Type}) + allMembers = append(allMembers, &common.Owner{Login: member.Login, ID: member.ID, Type: member.Type}) } if resp.NextPage == 0 { break diff --git a/main.go b/main.go index 5c5671d3..de8f275a 100644 --- a/main.go +++ b/main.go @@ -7,9 +7,9 @@ import ( "sync" "time" + "github.com/codeEmitter/gitrob/common" "github.com/codeEmitter/gitrob/core" "github.com/codeEmitter/gitrob/github" - "github.com/codeEmitter/gitrob/scm" ) var ( @@ -44,7 +44,7 @@ func GatherTargets(sess *core.Session) { } func GatherRepositories(sess *core.Session) { - var ch = make(chan *scm.Owner, len(sess.Targets)) + var ch = make(chan *common.Owner, len(sess.Targets)) var wg sync.WaitGroup var threadNum int if len(sess.Targets) == 1 { @@ -90,7 +90,7 @@ func GatherRepositories(sess *core.Session) { func AnalyzeRepositories(sess *core.Session) { sess.Stats.Status = core.StatusAnalyzing - var ch = make(chan *scm.Repository, len(sess.Repositories)) + var ch = make(chan *common.Repository, len(sess.Repositories)) var wg sync.WaitGroup var threadNum int if len(sess.Repositories) <= 1 { From 001fcd44f34a9e74b5922dcb6a06696ce29f421a Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 2 Mar 2020 17:54:48 -0500 Subject: [PATCH 022/226] properly set the user agent --- core/session.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/session.go b/core/session.go index 22498574..7c13950d 100644 --- a/core/session.go +++ b/core/session.go @@ -146,6 +146,7 @@ func (s *Session) ValidateTokenConfig() { } func (s *Session) InitAPIClient() { + userAgent := fmt.Sprintf("%s v%s", Name, Version) if s.GithubAccessToken != "" { ctx := context.Background() ts := oauth2.StaticTokenSource( @@ -153,10 +154,11 @@ func (s *Session) InitAPIClient() { ) tc := oauth2.NewClient(ctx, ts) s.GithubClient = github.NewClient(tc) - s.GithubClient.UserAgent = fmt.Sprintf("%s v%s", Name, Version) + s.GithubClient.UserAgent = userAgent } if s.GitLabAccessToken != "" { - s.GitLabClient = gitlab.NewClient(nil, s.GitLabAccessToken) + client := gitlab.NewClient(nil, s.GitLabAccessToken) + client.UserAgent = userAgent } } From 42d6ba625174b3edea8329a586f3b78792fd3785 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 2 Mar 2020 18:09:04 -0500 Subject: [PATCH 023/226] WIP: stubb in gitlab package exported method --- gitlab/default.go | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 gitlab/default.go diff --git a/gitlab/default.go b/gitlab/default.go new file mode 100644 index 00000000..2eb9ee27 --- /dev/null +++ b/gitlab/default.go @@ -0,0 +1,10 @@ +package gitlab + +import ( + "github.com/codeEmitter/gitrob/common" + "github.com/xanzy/go-gitlab" +) + +func GetUserOrOrganization(login string, client *gitlab.Client) (*common.Owner, error) { + +} From 898198b74e1e33aed6e9ad6fc81e566479c8cbb3 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 3 Mar 2020 16:13:38 -0500 Subject: [PATCH 024/226] get user information --- core/session.go | 4 ++-- gitlab/default.go | 28 ++++++++++++++++++++++++++++ main.go | 11 ++++++++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/core/session.go b/core/session.go index 7c13950d..59159eed 100644 --- a/core/session.go +++ b/core/session.go @@ -157,8 +157,8 @@ func (s *Session) InitAPIClient() { s.GithubClient.UserAgent = userAgent } if s.GitLabAccessToken != "" { - client := gitlab.NewClient(nil, s.GitLabAccessToken) - client.UserAgent = userAgent + s.GitLabClient = gitlab.NewClient(nil, s.GitLabAccessToken) + s.GitLabClient.UserAgent = userAgent } } diff --git a/gitlab/default.go b/gitlab/default.go index 2eb9ee27..94ed49c7 100644 --- a/gitlab/default.go +++ b/gitlab/default.go @@ -5,6 +5,34 @@ import ( "github.com/xanzy/go-gitlab" ) +func getUser(login string, client *gitlab.Client) ([]*gitlab.User, error) { + user, _, err := client.Users.ListUsers(&gitlab.ListUsersOptions{Username: gitlab.String(login)}) + if err != nil { + return nil, err + } + return user, err +} + func GetUserOrOrganization(login string, client *gitlab.Client) (*common.Owner, error) { + users, err := getUser(login, client) + if err != nil { + return nil, err + } + + id := int64(users[0].ID) + emptyString := gitlab.String("") + return &common.Owner{ + Login: gitlab.String(users[0].Username), + ID: &id, + Type: gitlab.String("User"), + Name: gitlab.String(users[0].Name), + AvatarURL: gitlab.String(users[0].AvatarURL), + URL: gitlab.String(users[0].WebsiteURL), + Company: gitlab.String(users[0].Organization), + Blog: emptyString, + Location: emptyString, + Email: gitlab.String(users[0].PublicEmail), + Bio: gitlab.String(users[0].Bio), + }, nil } diff --git a/main.go b/main.go index de8f275a..01f2bcc4 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "github.com/codeEmitter/gitrob/common" "github.com/codeEmitter/gitrob/core" "github.com/codeEmitter/gitrob/github" + "github.com/codeEmitter/gitrob/gitlab" ) var ( @@ -20,8 +21,16 @@ var ( func GatherTargets(sess *core.Session) { sess.Stats.Status = core.StatusGathering sess.Out.Important("Gathering targets...\n") + for _, login := range sess.Options.Logins { - target, err := github.GetUserOrOrganization(login, sess.GithubClient) + target, err := func() (*common.Owner, error) { + if sess.GithubAccessToken != "" { + return github.GetUserOrOrganization(login, sess.GithubClient) + } else { + return gitlab.GetUserOrOrganization(login, sess.GitLabClient) + } + }() + if err != nil { sess.Out.Error(" Error retrieving information on %s: %s\n", login, err) continue From 2e84d3bc5adabdcff476bde2e52175a1002e8a2e Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 3 Mar 2020 16:40:06 -0500 Subject: [PATCH 025/226] return a single user and simplify --- gitlab/default.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/gitlab/default.go b/gitlab/default.go index 94ed49c7..610deaa3 100644 --- a/gitlab/default.go +++ b/gitlab/default.go @@ -5,34 +5,34 @@ import ( "github.com/xanzy/go-gitlab" ) -func getUser(login string, client *gitlab.Client) ([]*gitlab.User, error) { +func getUser(login string, client *gitlab.Client) (*gitlab.User, error) { user, _, err := client.Users.ListUsers(&gitlab.ListUsersOptions{Username: gitlab.String(login)}) if err != nil { return nil, err } - return user, err + return user[0], err } func GetUserOrOrganization(login string, client *gitlab.Client) (*common.Owner, error) { - users, err := getUser(login, client) + user, err := getUser(login, client) if err != nil { return nil, err } - id := int64(users[0].ID) + id := int64(user.ID) emptyString := gitlab.String("") return &common.Owner{ - Login: gitlab.String(users[0].Username), + Login: gitlab.String(user.Username), ID: &id, Type: gitlab.String("User"), - Name: gitlab.String(users[0].Name), - AvatarURL: gitlab.String(users[0].AvatarURL), - URL: gitlab.String(users[0].WebsiteURL), - Company: gitlab.String(users[0].Organization), + Name: gitlab.String(user.Name), + AvatarURL: gitlab.String(user.AvatarURL), + URL: gitlab.String(user.WebsiteURL), + Company: gitlab.String(user.Organization), Blog: emptyString, Location: emptyString, - Email: gitlab.String(users[0].PublicEmail), - Bio: gitlab.String(users[0].Bio), + Email: gitlab.String(user.PublicEmail), + Bio: gitlab.String(user.Bio), }, nil } From 5642b8b6a5ca9f14d0f843d7bd04fb1f7ed6a02c Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 3 Mar 2020 16:58:28 -0500 Subject: [PATCH 026/226] namespace gitlab/github structs on session --- core/session.go | 59 ++++++++++++++++++++++++++++--------------------- main.go | 10 ++++----- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/core/session.go b/core/session.go index 59159eed..298091ea 100644 --- a/core/session.go +++ b/core/session.go @@ -41,21 +41,30 @@ type Stats struct { Findings int } +type Github struct { + AccessToken string `json:"-"` + Client *github.Client `json:"-"` +} + +type GitLab struct { + AccessToken string `json:"-"` + Client *gitlab.Client + UserID int64 +} + type Session struct { sync.Mutex - Version string - Options Options `json:"-"` - Out *Logger `json:"-"` - Stats *Stats - GithubAccessToken string `json:"-"` - GitLabAccessToken string `json:"-"` - GithubClient *github.Client `json:"-"` - GitLabClient *gitlab.Client - Router *gin.Engine `json:"-"` - Targets []*common.Owner - Repositories []*common.Repository - Findings []*Finding + Version string + Options Options `json:"-"` + Out *Logger `json:"-"` + Stats *Stats + Github Github + GitLab GitLab + Router *gin.Engine `json:"-"` + Targets []*common.Owner + Repositories []*common.Repository + Findings []*Finding } func (s *Session) Start() { @@ -125,40 +134,40 @@ func (s *Session) InitLogger() { func (s *Session) InitAccessToken() { if *s.Options.GithubAccessToken == "" { - s.GithubAccessToken = os.Getenv(GitHubAccessTokenEnvVariable) + s.Github.AccessToken = os.Getenv(GitHubAccessTokenEnvVariable) } else { - s.GithubAccessToken = *s.Options.GithubAccessToken + s.Github.AccessToken = *s.Options.GithubAccessToken } if *s.Options.GitLabAccessToken == "" { - s.GitLabAccessToken = os.Getenv(GitLabAccessTokenEnvVariable) + s.GitLab.AccessToken = os.Getenv(GitLabAccessTokenEnvVariable) } else { - s.GitLabAccessToken = *s.Options.GitLabAccessToken + s.GitLab.AccessToken = *s.Options.GitLabAccessToken } } func (s *Session) ValidateTokenConfig() { - if s.GitLabAccessToken != "" && s.GithubAccessToken != "" { + if s.GitLab.AccessToken != "" && s.Github.AccessToken != "" { s.Out.Fatal("Both a GitLab and Github token are present. Only one may be set.") } - if s.GitLabAccessToken == "" && s.GithubAccessToken == "" { + if s.GitLab.AccessToken == "" && s.Github.AccessToken == "" { s.Out.Fatal("No valid API token was found.") } } func (s *Session) InitAPIClient() { userAgent := fmt.Sprintf("%s v%s", Name, Version) - if s.GithubAccessToken != "" { + if s.Github.AccessToken != "" { ctx := context.Background() ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: s.GithubAccessToken}, + &oauth2.Token{AccessToken: s.Github.AccessToken}, ) tc := oauth2.NewClient(ctx, ts) - s.GithubClient = github.NewClient(tc) - s.GithubClient.UserAgent = userAgent + s.Github.Client = github.NewClient(tc) + s.Github.Client.UserAgent = userAgent } - if s.GitLabAccessToken != "" { - s.GitLabClient = gitlab.NewClient(nil, s.GitLabAccessToken) - s.GitLabClient.UserAgent = userAgent + if s.GitLab.AccessToken != "" { + s.GitLab.Client = gitlab.NewClient(nil, s.GitLab.AccessToken) + s.GitLab.Client.UserAgent = userAgent } } diff --git a/main.go b/main.go index 01f2bcc4..e56dd4d0 100644 --- a/main.go +++ b/main.go @@ -24,10 +24,10 @@ func GatherTargets(sess *core.Session) { for _, login := range sess.Options.Logins { target, err := func() (*common.Owner, error) { - if sess.GithubAccessToken != "" { - return github.GetUserOrOrganization(login, sess.GithubClient) + if sess.Github.AccessToken != "" { + return github.GetUserOrOrganization(login, sess.Github.Client) } else { - return gitlab.GetUserOrOrganization(login, sess.GitLabClient) + return gitlab.GetUserOrOrganization(login, sess.GitLab.Client) } }() @@ -39,7 +39,7 @@ func GatherTargets(sess *core.Session) { sess.AddTarget(target) if *sess.Options.NoExpandOrgs == false && *target.Type == "Organization" { sess.Out.Debug("Gathering members of %s (ID: %d)...\n", *target.Login, *target.ID) - members, err := github.GetOrganizationMembers(target.Login, sess.GithubClient) + members, err := github.GetOrganizationMembers(target.Login, sess.Github.Client) if err != nil { sess.Out.Error(" Error retrieving members of %s: %s\n", *target.Login, err) continue @@ -73,7 +73,7 @@ func GatherRepositories(sess *core.Session) { wg.Done() return } - repos, err := github.GetRepositoriesFromOwner(target.Login, sess.GithubClient) + repos, err := github.GetRepositoriesFromOwner(target.Login, sess.Github.Client) if err != nil { sess.Out.Error(" Failed to retrieve repositories from %s: %s\n", *target.Login, err) } From d5d6d79276c06853b156c11f33c1af84bcd010d5 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Wed, 4 Mar 2020 11:09:54 -0500 Subject: [PATCH 027/226] apply some constants for reliability --- common/sourcecontrol.go | 5 +++++ main.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/common/sourcecontrol.go b/common/sourcecontrol.go index 9010bf8c..10daf6b6 100644 --- a/common/sourcecontrol.go +++ b/common/sourcecontrol.go @@ -1,5 +1,10 @@ package common +const ( + TargetTypeUser = "User" + TargetTypeOrganization = "Organization" +) + type Owner struct { Login *string ID *int64 diff --git a/main.go b/main.go index e56dd4d0..00ab1b48 100644 --- a/main.go +++ b/main.go @@ -37,7 +37,7 @@ func GatherTargets(sess *core.Session) { } sess.Out.Debug("%s (ID: %d) type: %s\n", *target.Login, *target.ID, *target.Type) sess.AddTarget(target) - if *sess.Options.NoExpandOrgs == false && *target.Type == "Organization" { + if *sess.Options.NoExpandOrgs == false && *target.Type == common.TargetTypeOrganization { sess.Out.Debug("Gathering members of %s (ID: %d)...\n", *target.Login, *target.ID) members, err := github.GetOrganizationMembers(target.Login, sess.Github.Client) if err != nil { From e1d13438133a2c5231d0905a4f18bc9466154e89 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Wed, 4 Mar 2020 11:11:43 -0500 Subject: [PATCH 028/226] try to retieve an org first, if that fails try for a user --- gitlab/default.go | 61 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/gitlab/default.go b/gitlab/default.go index 610deaa3..03a098fe 100644 --- a/gitlab/default.go +++ b/gitlab/default.go @@ -1,6 +1,8 @@ package gitlab import ( + "strconv" + "github.com/codeEmitter/gitrob/common" "github.com/xanzy/go-gitlab" ) @@ -13,26 +15,53 @@ func getUser(login string, client *gitlab.Client) (*gitlab.User, error) { return user[0], err } -func GetUserOrOrganization(login string, client *gitlab.Client) (*common.Owner, error) { - user, err := getUser(login, client) +func getOrganization(login string, client *gitlab.Client) (*gitlab.Group, error) { + id, err := strconv.Atoi(login) if err != nil { return nil, err } + org, _, err := client.Groups.GetGroup(id) + return org, err +} - id := int64(user.ID) +func GetUserOrOrganization(login string, client *gitlab.Client) (*common.Owner, error) { emptyString := gitlab.String("") + org, orgErr := getOrganization(login, client) + if orgErr != nil { + user, userErr := getUser(login, client) + if userErr != nil { + return nil, userErr + } + id := int64(user.ID) + return &common.Owner{ + Login: gitlab.String(user.Username), + ID: &id, + Type: gitlab.String(common.TargetTypeUser), + Name: gitlab.String(user.Name), + AvatarURL: gitlab.String(user.AvatarURL), + URL: gitlab.String(user.WebsiteURL), + Company: gitlab.String(user.Organization), + Blog: emptyString, + Location: emptyString, + Email: gitlab.String(user.PublicEmail), + Bio: gitlab.String(user.Bio), + }, nil + } else { + id := int64(org.ID) + return &common.Owner{ + Login: gitlab.String(org.Name), + ID: &id, + Type: gitlab.String(common.TargetTypeOrganization), + Name: gitlab.String(org.Name), + AvatarURL: gitlab.String(org.AvatarURL), + URL: gitlab.String(org.WebURL), + Company: gitlab.String(org.FullName), + Blog: emptyString, + Location: emptyString, + Email: emptyString, + Bio: gitlab.String(org.Description), + }, nil + } +} - return &common.Owner{ - Login: gitlab.String(user.Username), - ID: &id, - Type: gitlab.String("User"), - Name: gitlab.String(user.Name), - AvatarURL: gitlab.String(user.AvatarURL), - URL: gitlab.String(user.WebsiteURL), - Company: gitlab.String(user.Organization), - Blog: emptyString, - Location: emptyString, - Email: gitlab.String(user.PublicEmail), - Bio: gitlab.String(user.Bio), - }, nil } From d621e935a469349829dd5f1b76e16f91f2c61050 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Wed, 4 Mar 2020 11:12:12 -0500 Subject: [PATCH 029/226] stub in retrieval of org members based on available token --- gitlab/default.go | 2 ++ main.go | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/gitlab/default.go b/gitlab/default.go index 03a098fe..b8a5fd12 100644 --- a/gitlab/default.go +++ b/gitlab/default.go @@ -64,4 +64,6 @@ func GetUserOrOrganization(login string, client *gitlab.Client) (*common.Owner, } } +func GetOrganizationMembers(login *string, client *gitlab.Client) ([]*common.Owner, error) { + return nil, nil } diff --git a/main.go b/main.go index 00ab1b48..08d303ac 100644 --- a/main.go +++ b/main.go @@ -39,7 +39,13 @@ func GatherTargets(sess *core.Session) { sess.AddTarget(target) if *sess.Options.NoExpandOrgs == false && *target.Type == common.TargetTypeOrganization { sess.Out.Debug("Gathering members of %s (ID: %d)...\n", *target.Login, *target.ID) - members, err := github.GetOrganizationMembers(target.Login, sess.Github.Client) + members, err := func() ([]*common.Owner, error) { + if sess.Github.AccessToken != "" { + return github.GetOrganizationMembers(target.Login, sess.Github.Client) + } else { + return gitlab.GetOrganizationMembers(target.Login, sess.GitLab.Client) + } + }() if err != nil { sess.Out.Error(" Error retrieving members of %s: %s\n", *target.Login, err) continue From 3d93bd19bae16bcbf89343eec3e1e22e00bb8c09 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Wed, 4 Mar 2020 11:13:11 -0500 Subject: [PATCH 030/226] ignore debug binary output --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7b5b9dee..e9bdbf3e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ gitrob.exe vendor/ build/ .vscode +__debug_bin # Test binary, build with `go test -c` *.test From 6334bcee0fa291c5376338ee0ba7309c88087a8a Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Wed, 4 Mar 2020 11:56:17 -0500 Subject: [PATCH 031/226] better error handling --- gitlab/default.go | 26 ++++++++++++++++++++++++-- main.go | 2 +- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/gitlab/default.go b/gitlab/default.go index b8a5fd12..bc4140b9 100644 --- a/gitlab/default.go +++ b/gitlab/default.go @@ -1,18 +1,37 @@ package gitlab import ( + "fmt" "strconv" + "strings" "github.com/codeEmitter/gitrob/common" "github.com/xanzy/go-gitlab" ) +type errorString struct { + s string +} + +func (e *errorString) Error() string { + return e.s +} +func new(text string) error { + return &errorString{text} +} + func getUser(login string, client *gitlab.Client) (*gitlab.User, error) { - user, _, err := client.Users.ListUsers(&gitlab.ListUsersOptions{Username: gitlab.String(login)}) + users, _, err := client.Users.ListUsers(&gitlab.ListUsersOptions{Username: gitlab.String(login)}) if err != nil { return nil, err } - return user[0], err + if len(users) == 0 { + return nil, new(fmt.Sprintf("No GitLab %s or %s %s was found.", + strings.ToLower(common.TargetTypeUser), + strings.ToLower(common.TargetTypeOrganization), + login)) + } + return users[0], err } func getOrganization(login string, client *gitlab.Client) (*gitlab.Group, error) { @@ -21,6 +40,9 @@ func getOrganization(login string, client *gitlab.Client) (*gitlab.Group, error) return nil, err } org, _, err := client.Groups.GetGroup(id) + if err != nil { + return nil, err + } return org, err } diff --git a/main.go b/main.go index 08d303ac..261e984f 100644 --- a/main.go +++ b/main.go @@ -31,7 +31,7 @@ func GatherTargets(sess *core.Session) { } }() - if err != nil { + if err != nil || target == nil { sess.Out.Error(" Error retrieving information on %s: %s\n", login, err) continue } From b33fcb40ab157e5994e139da936a35e94ee0cf65 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Wed, 4 Mar 2020 14:42:22 -0500 Subject: [PATCH 032/226] get organization members from gitlab group id --- gitlab/default.go | 24 ++++++++++++++++++++++-- main.go | 2 +- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/gitlab/default.go b/gitlab/default.go index bc4140b9..c527c222 100644 --- a/gitlab/default.go +++ b/gitlab/default.go @@ -86,6 +86,26 @@ func GetUserOrOrganization(login string, client *gitlab.Client) (*common.Owner, } } -func GetOrganizationMembers(login *string, client *gitlab.Client) ([]*common.Owner, error) { - return nil, nil +func GetOrganizationMembers(id int64, client *gitlab.Client) ([]*common.Owner, error) { + var allMembers []*common.Owner + opt := gitlab.ListGroupMembersOptions{} + for { + members, resp, err := client.Groups.ListAllGroupMembers(int(id), &gitlab.ListGroupMembersOptions{}) + if err != nil { + return nil, err + } + for _, member := range members { + id := int64(member.ID) + allMembers = append(allMembers, + &common.Owner{ + Login: gitlab.String(member.Username), + ID: &id, + Type: gitlab.String(common.TargetTypeUser)}) + } + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + return allMembers, nil } diff --git a/main.go b/main.go index 261e984f..7048cb11 100644 --- a/main.go +++ b/main.go @@ -43,7 +43,7 @@ func GatherTargets(sess *core.Session) { if sess.Github.AccessToken != "" { return github.GetOrganizationMembers(target.Login, sess.Github.Client) } else { - return gitlab.GetOrganizationMembers(target.Login, sess.GitLab.Client) + return gitlab.GetOrganizationMembers(*target.ID, sess.GitLab.Client) } }() if err != nil { From ce1bd769233bde1b16b6635ae9c11a4c661508ef Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Wed, 4 Mar 2020 17:33:10 -0500 Subject: [PATCH 033/226] WIP get gitlab repository info --- gitlab/default.go | 50 +++++++++++++++++++++++++++++++++++++++++++++-- main.go | 8 +++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/gitlab/default.go b/gitlab/default.go index c527c222..598436cd 100644 --- a/gitlab/default.go +++ b/gitlab/default.go @@ -88,9 +88,9 @@ func GetUserOrOrganization(login string, client *gitlab.Client) (*common.Owner, func GetOrganizationMembers(id int64, client *gitlab.Client) ([]*common.Owner, error) { var allMembers []*common.Owner - opt := gitlab.ListGroupMembersOptions{} + opt := &gitlab.ListGroupMembersOptions{} for { - members, resp, err := client.Groups.ListAllGroupMembers(int(id), &gitlab.ListGroupMembersOptions{}) + members, resp, err := client.Groups.ListAllGroupMembers(int(id), opt) if err != nil { return nil, err } @@ -109,3 +109,49 @@ func GetOrganizationMembers(id int64, client *gitlab.Client) ([]*common.Owner, e } return allMembers, nil } + +func GetRepositoriesFromOwner(target common.Owner, client *gitlab.Client) ([]*common.Repository, error) { + var allProjects []*common.Repository + listUserProjectsOps := &gitlab.ListProjectsOptions{} + listGroupProjectsOps := &gitlab.ListGroupProjectsOptions{} + id := int(*target.ID) + for { + projects, resp, err := func() ([]*gitlab.Project, *gitlab.Response, error) { + if *target.Type == common.TargetTypeUser { + return client.Projects.ListUserProjects(id, listUserProjectsOps) + } else { + return client.Groups.ListGroupProjects(id, listGroupProjectsOps) + } + }() + if err != nil { + return nil, err + } + for _, project := range projects { + //don't capture forks + if (gitlab.ForkParent{}) == *project.ForkedFromProject { + id := int64(project.ID) + p := common.Repository{ + Owner: gitlab.String(project.Owner.Name), + ID: &id, + Name: gitlab.String(project.Name), + FullName: gitlab.String(project.NameWithNamespace), + CloneURL: gitlab.String(project.HTTPURLToRepo), + URL: gitlab.String(project.WebURL), + DefaultBranch: gitlab.String(project.DefaultBranch), + Description: gitlab.String(project.Description), + Homepage: gitlab.String(project.WebURL), + } + allProjects = append(allProjects, &p) + } + } + if resp.NextPage == 0 { + break + } + if *target.Type == common.TargetTypeUser { + listUserProjectsOps.Page = resp.NextPage + } else { + listGroupProjectsOps.Page = resp.NextPage + } + } + return allProjects, nil +} diff --git a/main.go b/main.go index 7048cb11..ff7950d1 100644 --- a/main.go +++ b/main.go @@ -79,7 +79,13 @@ func GatherRepositories(sess *core.Session) { wg.Done() return } - repos, err := github.GetRepositoriesFromOwner(target.Login, sess.Github.Client) + repos, err := func() ([]*common.Repository, error) { + if sess.Github.AccessToken != "" { + return github.GetRepositoriesFromOwner(target.Login, sess.Github.Client) + } else { + return gitlab.GetRepositoriesFromOwner(*target, sess.GitLab.Client) + } + }() if err != nil { sess.Out.Error(" Failed to retrieve repositories from %s: %s\n", *target.Login, err) } From 0a3143b828fbc3cb94008d37b56c53a4998e8e28 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 5 Mar 2020 14:35:55 -0500 Subject: [PATCH 034/226] compose functions to retrieve user and group repos in thread isolation --- gitlab/default.go | 83 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 17 deletions(-) diff --git a/gitlab/default.go b/gitlab/default.go index 598436cd..fca5d013 100644 --- a/gitlab/default.go +++ b/gitlab/default.go @@ -110,25 +110,17 @@ func GetOrganizationMembers(id int64, client *gitlab.Client) ([]*common.Owner, e return allMembers, nil } -func GetRepositoriesFromOwner(target common.Owner, client *gitlab.Client) ([]*common.Repository, error) { - var allProjects []*common.Repository +func getUserProjects(id int, client *gitlab.Client) ([]*common.Repository, error) { + var allUserProjects []*common.Repository listUserProjectsOps := &gitlab.ListProjectsOptions{} - listGroupProjectsOps := &gitlab.ListGroupProjectsOptions{} - id := int(*target.ID) for { - projects, resp, err := func() ([]*gitlab.Project, *gitlab.Response, error) { - if *target.Type == common.TargetTypeUser { - return client.Projects.ListUserProjects(id, listUserProjectsOps) - } else { - return client.Groups.ListGroupProjects(id, listGroupProjectsOps) - } - }() + projects, resp, err := client.Projects.ListUserProjects(id, listUserProjectsOps) if err != nil { return nil, err } for _, project := range projects { //don't capture forks - if (gitlab.ForkParent{}) == *project.ForkedFromProject { + if project.ForkedFromProject == nil { id := int64(project.ID) p := common.Repository{ Owner: gitlab.String(project.Owner.Name), @@ -141,16 +133,73 @@ func GetRepositoriesFromOwner(target common.Owner, client *gitlab.Client) ([]*co Description: gitlab.String(project.Description), Homepage: gitlab.String(project.WebURL), } - allProjects = append(allProjects, &p) + allUserProjects = append(allUserProjects, &p) + } + } + if resp.NextPage == 0 { + break + } + listUserProjectsOps.Page = resp.NextPage + } + return allUserProjects, nil +} + +func getGroupProjects(id int, client *gitlab.Client) ([]*common.Repository, error) { + var allGroupProjects []*common.Repository + listGroupProjectsOps := &gitlab.ListGroupProjectsOptions{} + for { + projects, resp, err := client.Groups.ListGroupProjects(id, listGroupProjectsOps) + if err != nil { + return nil, err + } + for _, project := range projects { + //don't capture forks + if project.ForkedFromProject == nil { + id := int64(project.ID) + owner := "" + if project.Owner != nil { + owner = project.Owner.Name + } + p := common.Repository{ + Owner: gitlab.String(owner), + ID: &id, + Name: gitlab.String(project.Name), + FullName: gitlab.String(project.NameWithNamespace), + CloneURL: gitlab.String(project.HTTPURLToRepo), + URL: gitlab.String(project.WebURL), + DefaultBranch: gitlab.String(project.DefaultBranch), + Description: gitlab.String(project.Description), + Homepage: gitlab.String(project.WebURL), + } + allGroupProjects = append(allGroupProjects, &p) } } if resp.NextPage == 0 { break } - if *target.Type == common.TargetTypeUser { - listUserProjectsOps.Page = resp.NextPage - } else { - listGroupProjectsOps.Page = resp.NextPage + listGroupProjectsOps.Page = resp.NextPage + } + return allGroupProjects, nil +} + +func GetRepositoriesFromOwner(target common.Owner, client *gitlab.Client) ([]*common.Repository, error) { + var allProjects []*common.Repository + id := int(*target.ID) + if *target.Type == common.TargetTypeUser { + userProjects, err := getUserProjects(id, client) + if err != nil { + return nil, err + } + for _, project := range userProjects { + allProjects = append(allProjects, project) + } + } else { + groupProjects, err := getGroupProjects(id, client) + if err != nil { + return nil, err + } + for _, project := range groupProjects { + allProjects = append(allProjects, project) } } return allProjects, nil From 66ab67173367c0c2774395671c1257fb390ea4df Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 5 Mar 2020 15:02:23 -0500 Subject: [PATCH 035/226] better file names --- github/{default.go => targets.go} | 0 gitlab/{default.go => targets.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename github/{default.go => targets.go} (100%) rename gitlab/{default.go => targets.go} (100%) diff --git a/github/default.go b/github/targets.go similarity index 100% rename from github/default.go rename to github/targets.go diff --git a/gitlab/default.go b/gitlab/targets.go similarity index 100% rename from gitlab/default.go rename to gitlab/targets.go From 94d3b9703bbda90d2ef35150c8bf5a06d90c2b9d Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 5 Mar 2020 15:08:26 -0500 Subject: [PATCH 036/226] repackage git functionality specific to github --- {core => github}/git.go | 2 +- main.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename {core => github}/git.go (99%) diff --git a/core/git.go b/github/git.go similarity index 99% rename from core/git.go rename to github/git.go index 2e780898..5c90a0d3 100644 --- a/core/git.go +++ b/github/git.go @@ -1,4 +1,4 @@ -package core +package github import ( "fmt" diff --git a/main.go b/main.go index ff7950d1..d41f43cc 100644 --- a/main.go +++ b/main.go @@ -138,7 +138,7 @@ func AnalyzeRepositories(sess *core.Session) { } sess.Out.Debug("[THREAD #%d][%s] Cloning repository...\n", tid, *repo.FullName) - clone, path, err := core.CloneRepository(repo.CloneURL, repo.DefaultBranch, *sess.Options.CommitDepth) + clone, path, err := github.CloneRepository(repo.CloneURL, repo.DefaultBranch, *sess.Options.CommitDepth) if err != nil { if err.Error() != "remote repository is empty" { sess.Out.Error("Error cloning repository %s: %s\n", *repo.FullName, err) @@ -149,7 +149,7 @@ func AnalyzeRepositories(sess *core.Session) { } sess.Out.Debug("[THREAD #%d][%s] Cloned repository to: %s\n", tid, *repo.FullName, path) - history, err := core.GetRepositoryHistory(clone) + history, err := github.GetRepositoryHistory(clone) if err != nil { sess.Out.Error("[THREAD #%d][%s] Error getting commit history: %s\n", tid, *repo.FullName, err) os.RemoveAll(path) @@ -161,11 +161,11 @@ func AnalyzeRepositories(sess *core.Session) { for _, commit := range history { sess.Out.Debug("[THREAD #%d][%s] Analyzing commit: %s\n", tid, *repo.FullName, commit.Hash) - changes, _ := core.GetChanges(commit, clone) + changes, _ := github.GetChanges(commit, clone) sess.Out.Debug("[THREAD #%d][%s] Changes in %s: %d\n", tid, *repo.FullName, commit.Hash, len(changes)) for _, change := range changes { - changeAction := core.GetChangeAction(change) - path := core.GetChangePath(change) + changeAction := github.GetChangeAction(change) + path := github.GetChangePath(change) matchFile := core.NewMatchFile(path) if matchFile.IsSkippable() { sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.FullName, matchFile.Path) From a99aeba530fe2fe7b39b66e89dbf6e0a870a94ea Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 6 Mar 2020 11:13:44 -0500 Subject: [PATCH 037/226] group private functions together --- gitlab/targets.go | 128 +++++++++++++++++++++++----------------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/gitlab/targets.go b/gitlab/targets.go index fca5d013..97e3aa0f 100644 --- a/gitlab/targets.go +++ b/gitlab/targets.go @@ -46,70 +46,6 @@ func getOrganization(login string, client *gitlab.Client) (*gitlab.Group, error) return org, err } -func GetUserOrOrganization(login string, client *gitlab.Client) (*common.Owner, error) { - emptyString := gitlab.String("") - org, orgErr := getOrganization(login, client) - if orgErr != nil { - user, userErr := getUser(login, client) - if userErr != nil { - return nil, userErr - } - id := int64(user.ID) - return &common.Owner{ - Login: gitlab.String(user.Username), - ID: &id, - Type: gitlab.String(common.TargetTypeUser), - Name: gitlab.String(user.Name), - AvatarURL: gitlab.String(user.AvatarURL), - URL: gitlab.String(user.WebsiteURL), - Company: gitlab.String(user.Organization), - Blog: emptyString, - Location: emptyString, - Email: gitlab.String(user.PublicEmail), - Bio: gitlab.String(user.Bio), - }, nil - } else { - id := int64(org.ID) - return &common.Owner{ - Login: gitlab.String(org.Name), - ID: &id, - Type: gitlab.String(common.TargetTypeOrganization), - Name: gitlab.String(org.Name), - AvatarURL: gitlab.String(org.AvatarURL), - URL: gitlab.String(org.WebURL), - Company: gitlab.String(org.FullName), - Blog: emptyString, - Location: emptyString, - Email: emptyString, - Bio: gitlab.String(org.Description), - }, nil - } -} - -func GetOrganizationMembers(id int64, client *gitlab.Client) ([]*common.Owner, error) { - var allMembers []*common.Owner - opt := &gitlab.ListGroupMembersOptions{} - for { - members, resp, err := client.Groups.ListAllGroupMembers(int(id), opt) - if err != nil { - return nil, err - } - for _, member := range members { - id := int64(member.ID) - allMembers = append(allMembers, - &common.Owner{ - Login: gitlab.String(member.Username), - ID: &id, - Type: gitlab.String(common.TargetTypeUser)}) - } - if resp.NextPage == 0 { - break - } - opt.Page = resp.NextPage - } - return allMembers, nil -} - func getUserProjects(id int, client *gitlab.Client) ([]*common.Repository, error) { var allUserProjects []*common.Repository listUserProjectsOps := &gitlab.ListProjectsOptions{} @@ -182,6 +118,70 @@ func getGroupProjects(id int, client *gitlab.Client) ([]*common.Repository, erro return allGroupProjects, nil } +func GetUserOrOrganization(login string, client *gitlab.Client) (*common.Owner, error) { + emptyString := gitlab.String("") + org, orgErr := getOrganization(login, client) + if orgErr != nil { + user, userErr := getUser(login, client) + if userErr != nil { + return nil, userErr + } + id := int64(user.ID) + return &common.Owner{ + Login: gitlab.String(user.Username), + ID: &id, + Type: gitlab.String(common.TargetTypeUser), + Name: gitlab.String(user.Name), + AvatarURL: gitlab.String(user.AvatarURL), + URL: gitlab.String(user.WebsiteURL), + Company: gitlab.String(user.Organization), + Blog: emptyString, + Location: emptyString, + Email: gitlab.String(user.PublicEmail), + Bio: gitlab.String(user.Bio), + }, nil + } else { + id := int64(org.ID) + return &common.Owner{ + Login: gitlab.String(org.Name), + ID: &id, + Type: gitlab.String(common.TargetTypeOrganization), + Name: gitlab.String(org.Name), + AvatarURL: gitlab.String(org.AvatarURL), + URL: gitlab.String(org.WebURL), + Company: gitlab.String(org.FullName), + Blog: emptyString, + Location: emptyString, + Email: emptyString, + Bio: gitlab.String(org.Description), + }, nil + } +} + +func GetOrganizationMembers(id int64, client *gitlab.Client) ([]*common.Owner, error) { + var allMembers []*common.Owner + opt := &gitlab.ListGroupMembersOptions{} + for { + members, resp, err := client.Groups.ListAllGroupMembers(int(id), opt) + if err != nil { + return nil, err + } + for _, member := range members { + id := int64(member.ID) + allMembers = append(allMembers, + &common.Owner{ + Login: gitlab.String(member.Username), + ID: &id, + Type: gitlab.String(common.TargetTypeUser)}) + } + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + return allMembers, nil +} + func GetRepositoriesFromOwner(target common.Owner, client *gitlab.Client) ([]*common.Repository, error) { var allProjects []*common.Repository id := int(*target.ID) From 227ff9567110d167b2118cc5137bf65b726c6a96 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 6 Mar 2020 11:14:12 -0500 Subject: [PATCH 038/226] clone gitlab repos --- gitlab/git.go | 29 +++++++++++++++++++++++++++++ main.go | 12 ++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 gitlab/git.go diff --git a/gitlab/git.go b/gitlab/git.go new file mode 100644 index 00000000..3195cd68 --- /dev/null +++ b/gitlab/git.go @@ -0,0 +1,29 @@ +package gitlab + +import ( + "fmt" + "io/ioutil" + + "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing" +) + +func CloneRepository(url *string, branch *string, depth int) (*git.Repository, string, error) { + urlVal := *url + branchVal := *branch + dir, err := ioutil.TempDir("", "gitrob") + if err != nil { + return nil, "", err + } + repository, err := git.PlainClone(dir, false, &git.CloneOptions{ + URL: urlVal, + Depth: depth, + ReferenceName: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branchVal)), + SingleBranch: true, + Tags: git.NoTags, + }) + if err != nil { + return nil, dir, err + } + return repository, dir, nil +} diff --git a/main.go b/main.go index d41f43cc..c1ae61bb 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "github.com/codeEmitter/gitrob/core" "github.com/codeEmitter/gitrob/github" "github.com/codeEmitter/gitrob/gitlab" + "gopkg.in/src-d/go-git.v4" ) var ( @@ -137,8 +138,15 @@ func AnalyzeRepositories(sess *core.Session) { return } - sess.Out.Debug("[THREAD #%d][%s] Cloning repository...\n", tid, *repo.FullName) - clone, path, err := github.CloneRepository(repo.CloneURL, repo.DefaultBranch, *sess.Options.CommitDepth) + sess.Out.Debug("[THREAD #%d][%s] Cloning repository...\n", tid, *repo.CloneURL) + clone, path, err := func() (*git.Repository, string, error) { + if sess.Github.AccessToken != "" { + return github.CloneRepository(repo.CloneURL, repo.DefaultBranch, *sess.Options.CommitDepth) + } else { + return gitlab.CloneRepository(repo.CloneURL, repo.DefaultBranch, *sess.Options.CommitDepth) + } + }() + //clone, path, err := github.CloneRepository(repo.CloneURL, repo.DefaultBranch, *sess.Options.CommitDepth) if err != nil { if err.Error() != "remote repository is empty" { sess.Out.Error("Error cloning repository %s: %s\n", *repo.FullName, err) From 5fe41bc81aa9d88d46b8c13618ba983e657a951c Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 6 Mar 2020 12:20:46 -0500 Subject: [PATCH 039/226] authenticate to clone private gitlab repositories that the provided token has access to --- common/sourcecontrol.go | 8 ++++++++ gitlab/git.go | 16 ++++++++++------ main.go | 11 +++++++++-- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/common/sourcecontrol.go b/common/sourcecontrol.go index 10daf6b6..6fa41656 100644 --- a/common/sourcecontrol.go +++ b/common/sourcecontrol.go @@ -5,6 +5,14 @@ const ( TargetTypeOrganization = "Organization" ) +type CloneConfiguration struct { + Url *string + Username *string + Token *string + Branch *string + Depth *int +} + type Owner struct { Login *string ID *int64 diff --git a/gitlab/git.go b/gitlab/git.go index 3195cd68..c45fdaf4 100644 --- a/gitlab/git.go +++ b/gitlab/git.go @@ -4,23 +4,27 @@ import ( "fmt" "io/ioutil" + "github.com/codeEmitter/gitrob/common" "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/transport/http" ) -func CloneRepository(url *string, branch *string, depth int) (*git.Repository, string, error) { - urlVal := *url - branchVal := *branch +func CloneRepository(cloneConfig *common.CloneConfiguration) (*git.Repository, string, error) { dir, err := ioutil.TempDir("", "gitrob") if err != nil { return nil, "", err } repository, err := git.PlainClone(dir, false, &git.CloneOptions{ - URL: urlVal, - Depth: depth, - ReferenceName: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branchVal)), + URL: *cloneConfig.Url, + Depth: *cloneConfig.Depth, + ReferenceName: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", *cloneConfig.Branch)), SingleBranch: true, Tags: git.NoTags, + Auth: &http.BasicAuth{ + Username: *cloneConfig.Username, + Password: *cloneConfig.Token, + }, }) if err != nil { return nil, dir, err diff --git a/main.go b/main.go index c1ae61bb..6d3587b4 100644 --- a/main.go +++ b/main.go @@ -143,10 +143,17 @@ func AnalyzeRepositories(sess *core.Session) { if sess.Github.AccessToken != "" { return github.CloneRepository(repo.CloneURL, repo.DefaultBranch, *sess.Options.CommitDepth) } else { - return gitlab.CloneRepository(repo.CloneURL, repo.DefaultBranch, *sess.Options.CommitDepth) + userName := "oauth2" + cloneConfig := common.CloneConfiguration{ + Url: repo.CloneURL, + Branch: repo.DefaultBranch, + Depth: sess.Options.CommitDepth, + Token: &sess.GitLab.AccessToken, + Username: &userName, + } + return gitlab.CloneRepository(&cloneConfig) } }() - //clone, path, err := github.CloneRepository(repo.CloneURL, repo.DefaultBranch, *sess.Options.CommitDepth) if err != nil { if err.Error() != "remote repository is empty" { sess.Out.Error("Error cloning repository %s: %s\n", *repo.FullName, err) From 3762c52a22888022b18170e7b6f530e752f07e4d Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 6 Mar 2020 14:06:34 -0500 Subject: [PATCH 040/226] factor out common functions related to source control --- common/sourcecontrol.go | 96 +++++++++++++++++++++++++++++++++++++++++ main.go | 10 ++--- 2 files changed, 101 insertions(+), 5 deletions(-) diff --git a/common/sourcecontrol.go b/common/sourcecontrol.go index 6fa41656..35e5a03e 100644 --- a/common/sourcecontrol.go +++ b/common/sourcecontrol.go @@ -1,5 +1,12 @@ package common +import ( + "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/utils/merkletrie" +) + const ( TargetTypeUser = "User" TargetTypeOrganization = "Organization" @@ -38,3 +45,92 @@ type Repository struct { Description *string Homepage *string } + +const ( + EmptyTreeCommitId = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" +) + +func GetRepositoryHistory(repository *git.Repository) ([]*object.Commit, error) { + var commits []*object.Commit + ref, err := repository.Head() + if err != nil { + return nil, err + } + cIter, err := repository.Log(&git.LogOptions{From: ref.Hash()}) + if err != nil { + return nil, err + } + cIter.ForEach(func(c *object.Commit) error { + commits = append(commits, c) + return nil + }) + return commits, nil +} + +func GetChanges(commit *object.Commit, repo *git.Repository) (object.Changes, error) { + parentCommit, err := GetParentCommit(commit, repo) + if err != nil { + return nil, err + } + + commitTree, err := commit.Tree() + if err != nil { + return nil, err + } + + parentCommitTree, err := parentCommit.Tree() + if err != nil { + return nil, err + } + + changes, err := object.DiffTree(parentCommitTree, commitTree) + if err != nil { + return nil, err + } + return changes, nil +} + +func GetParentCommit(commit *object.Commit, repo *git.Repository) (*object.Commit, error) { + if commit.NumParents() == 0 { + parentCommit, err := repo.CommitObject(plumbing.NewHash(EmptyTreeCommitId)) + if err != nil { + return nil, err + } + return parentCommit, nil + } + parentCommit, err := commit.Parents().Next() + if err != nil { + return nil, err + } + return parentCommit, nil +} + +func GetChangeAction(change *object.Change) string { + action, err := change.Action() + if err != nil { + return "Unknown" + } + switch action { + case merkletrie.Insert: + return "Insert" + case merkletrie.Modify: + return "Modify" + case merkletrie.Delete: + return "Delete" + default: + return "Unknown" + } +} + +func GetChangePath(change *object.Change) string { + action, err := change.Action() + if err != nil { + return change.To.Name + } + + if action == merkletrie.Delete { + return change.From.Name + } else { + return change.To.Name + } +} diff --git a/main.go b/main.go index 6d3587b4..10ee38d3 100644 --- a/main.go +++ b/main.go @@ -156,7 +156,7 @@ func AnalyzeRepositories(sess *core.Session) { }() if err != nil { if err.Error() != "remote repository is empty" { - sess.Out.Error("Error cloning repository %s: %s\n", *repo.FullName, err) + sess.Out.Error("Error cloning repository %s: %s\n", *repo.CloneURL, err) } sess.Stats.IncrementRepositories() sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) @@ -164,7 +164,7 @@ func AnalyzeRepositories(sess *core.Session) { } sess.Out.Debug("[THREAD #%d][%s] Cloned repository to: %s\n", tid, *repo.FullName, path) - history, err := github.GetRepositoryHistory(clone) + history, err := common.GetRepositoryHistory(clone) if err != nil { sess.Out.Error("[THREAD #%d][%s] Error getting commit history: %s\n", tid, *repo.FullName, err) os.RemoveAll(path) @@ -176,11 +176,11 @@ func AnalyzeRepositories(sess *core.Session) { for _, commit := range history { sess.Out.Debug("[THREAD #%d][%s] Analyzing commit: %s\n", tid, *repo.FullName, commit.Hash) - changes, _ := github.GetChanges(commit, clone) + changes, _ := common.GetChanges(commit, clone) sess.Out.Debug("[THREAD #%d][%s] Changes in %s: %d\n", tid, *repo.FullName, commit.Hash, len(changes)) for _, change := range changes { - changeAction := github.GetChangeAction(change) - path := github.GetChangePath(change) + changeAction := common.GetChangeAction(change) + path := common.GetChangePath(change) matchFile := core.NewMatchFile(path) if matchFile.IsSkippable() { sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.FullName, matchFile.Path) From 13db8cb596a3daf037c025dae08f53dd5d225bb4 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 6 Mar 2020 14:12:23 -0500 Subject: [PATCH 041/226] set proper scope --- common/sourcecontrol.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/common/sourcecontrol.go b/common/sourcecontrol.go index 35e5a03e..d1fb4a12 100644 --- a/common/sourcecontrol.go +++ b/common/sourcecontrol.go @@ -50,6 +50,21 @@ const ( EmptyTreeCommitId = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" ) +func getParentCommit(commit *object.Commit, repo *git.Repository) (*object.Commit, error) { + if commit.NumParents() == 0 { + parentCommit, err := repo.CommitObject(plumbing.NewHash(EmptyTreeCommitId)) + if err != nil { + return nil, err + } + return parentCommit, nil + } + parentCommit, err := commit.Parents().Next() + if err != nil { + return nil, err + } + return parentCommit, nil +} + func GetRepositoryHistory(repository *git.Repository) ([]*object.Commit, error) { var commits []*object.Commit ref, err := repository.Head() @@ -68,7 +83,7 @@ func GetRepositoryHistory(repository *git.Repository) ([]*object.Commit, error) } func GetChanges(commit *object.Commit, repo *git.Repository) (object.Changes, error) { - parentCommit, err := GetParentCommit(commit, repo) + parentCommit, err := getParentCommit(commit, repo) if err != nil { return nil, err } @@ -90,21 +105,6 @@ func GetChanges(commit *object.Commit, repo *git.Repository) (object.Changes, er return changes, nil } -func GetParentCommit(commit *object.Commit, repo *git.Repository) (*object.Commit, error) { - if commit.NumParents() == 0 { - parentCommit, err := repo.CommitObject(plumbing.NewHash(EmptyTreeCommitId)) - if err != nil { - return nil, err - } - return parentCommit, nil - } - parentCommit, err := commit.Parents().Next() - if err != nil { - return nil, err - } - return parentCommit, nil -} - func GetChangeAction(change *object.Change) string { action, err := change.Action() if err != nil { From 9e3c508c67330e41e45e200e3a2826af0b52e9bd Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 6 Mar 2020 14:32:31 -0500 Subject: [PATCH 042/226] log the clone url for easier debugging --- main.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/main.go b/main.go index 10ee38d3..2fca2e0b 100644 --- a/main.go +++ b/main.go @@ -94,7 +94,7 @@ func GatherRepositories(sess *core.Session) { continue } for _, repo := range repos { - sess.Out.Debug(" Retrieved repository: %s\n", *repo.FullName) + sess.Out.Debug(" Retrieved repository: %s\n", *repo.CloneURL) sess.AddRepository(repo) } sess.Stats.IncrementTargets() @@ -162,31 +162,31 @@ func AnalyzeRepositories(sess *core.Session) { sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) continue } - sess.Out.Debug("[THREAD #%d][%s] Cloned repository to: %s\n", tid, *repo.FullName, path) + sess.Out.Debug("[THREAD #%d][%s] Cloned repository to: %s\n", tid, *repo.CloneURL, path) history, err := common.GetRepositoryHistory(clone) if err != nil { - sess.Out.Error("[THREAD #%d][%s] Error getting commit history: %s\n", tid, *repo.FullName, err) + sess.Out.Error("[THREAD #%d][%s] Error getting commit history: %s\n", tid, *repo.CloneURL, err) os.RemoveAll(path) sess.Stats.IncrementRepositories() sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) continue } - sess.Out.Debug("[THREAD #%d][%s] Number of commits: %d\n", tid, *repo.FullName, len(history)) + sess.Out.Debug("[THREAD #%d][%s] Number of commits: %d\n", tid, *repo.CloneURL, len(history)) for _, commit := range history { - sess.Out.Debug("[THREAD #%d][%s] Analyzing commit: %s\n", tid, *repo.FullName, commit.Hash) + sess.Out.Debug("[THREAD #%d][%s] Analyzing commit: %s\n", tid, *repo.CloneURL, commit.Hash) changes, _ := common.GetChanges(commit, clone) - sess.Out.Debug("[THREAD #%d][%s] Changes in %s: %d\n", tid, *repo.FullName, commit.Hash, len(changes)) + sess.Out.Debug("[THREAD #%d][%s] Changes in %s: %d\n", tid, *repo.CloneURL, commit.Hash, len(changes)) for _, change := range changes { changeAction := common.GetChangeAction(change) path := common.GetChangePath(change) matchFile := core.NewMatchFile(path) if matchFile.IsSkippable() { - sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.FullName, matchFile.Path) + sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.CloneURL, matchFile.Path) continue } - sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.FullName, matchFile.Path) + sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.CloneURL, matchFile.Path) for _, signature := range core.Signatures { if signature.Match(matchFile) { @@ -206,7 +206,7 @@ func AnalyzeRepositories(sess *core.Session) { sess.Out.Warn(" %s: %s\n", strings.ToUpper(changeAction), finding.Description) sess.Out.Info(" Path.......: %s\n", finding.FilePath) - sess.Out.Info(" Repo.......: %s\n", *repo.FullName) + sess.Out.Info(" Repo.......: %s\n", *repo.CloneURL) sess.Out.Info(" Message....: %s\n", core.TruncateString(finding.CommitMessage, 100)) sess.Out.Info(" Author.....: %s\n", finding.CommitAuthor) if finding.Comment != "" { @@ -222,11 +222,11 @@ func AnalyzeRepositories(sess *core.Session) { sess.Stats.IncrementFiles() } sess.Stats.IncrementCommits() - sess.Out.Debug("[THREAD #%d][%s] Done analyzing changes in %s\n", tid, *repo.FullName, commit.Hash) + sess.Out.Debug("[THREAD #%d][%s] Done analyzing changes in %s\n", tid, *repo.CloneURL, commit.Hash) } - sess.Out.Debug("[THREAD #%d][%s] Done analyzing commits\n", tid, *repo.FullName) + sess.Out.Debug("[THREAD #%d][%s] Done analyzing commits\n", tid, *repo.CloneURL) os.RemoveAll(path) - sess.Out.Debug("[THREAD #%d][%s] Deleted %s\n", tid, *repo.FullName, path) + sess.Out.Debug("[THREAD #%d][%s] Deleted %s\n", tid, *repo.CloneURL, path) sess.Stats.IncrementRepositories() sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) } From 035e7ee4b2c3f47b1a21639e165db4227fc3edbb Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 6 Mar 2020 14:33:34 -0500 Subject: [PATCH 043/226] cleanup moved functions --- github/git.go | 85 --------------------------------------------------- 1 file changed, 85 deletions(-) diff --git a/github/git.go b/github/git.go index 5c90a0d3..0fb3dc25 100644 --- a/github/git.go +++ b/github/git.go @@ -33,88 +33,3 @@ func CloneRepository(url *string, branch *string, depth int) (*git.Repository, s } return repository, dir, nil } - -func GetRepositoryHistory(repository *git.Repository) ([]*object.Commit, error) { - var commits []*object.Commit - ref, err := repository.Head() - if err != nil { - return nil, err - } - cIter, err := repository.Log(&git.LogOptions{From: ref.Hash()}) - if err != nil { - return nil, err - } - cIter.ForEach(func(c *object.Commit) error { - commits = append(commits, c) - return nil - }) - return commits, nil -} - -func GetChanges(commit *object.Commit, repo *git.Repository) (object.Changes, error) { - parentCommit, err := GetParentCommit(commit, repo) - if err != nil { - return nil, err - } - - commitTree, err := commit.Tree() - if err != nil { - return nil, err - } - - parentCommitTree, err := parentCommit.Tree() - if err != nil { - return nil, err - } - - changes, err := object.DiffTree(parentCommitTree, commitTree) - if err != nil { - return nil, err - } - return changes, nil -} - -func GetParentCommit(commit *object.Commit, repo *git.Repository) (*object.Commit, error) { - if commit.NumParents() == 0 { - parentCommit, err := repo.CommitObject(plumbing.NewHash(EmptyTreeCommitId)) - if err != nil { - return nil, err - } - return parentCommit, nil - } - parentCommit, err := commit.Parents().Next() - if err != nil { - return nil, err - } - return parentCommit, nil -} - -func GetChangeAction(change *object.Change) string { - action, err := change.Action() - if err != nil { - return "Unknown" - } - switch action { - case merkletrie.Insert: - return "Insert" - case merkletrie.Modify: - return "Modify" - case merkletrie.Delete: - return "Delete" - default: - return "Unknown" - } -} - -func GetChangePath(change *object.Change) string { - action, err := change.Action() - if err != nil { - return change.To.Name - } - - if action == merkletrie.Delete { - return change.From.Name - } else { - return change.To.Name - } -} From b3fd9f9f206227516bb4518d0008f9820a3ab487 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 6 Mar 2020 14:38:33 -0500 Subject: [PATCH 044/226] pass a clone configuration type fo github clones --- github/git.go | 18 ++++++------------ main.go | 16 ++++++++-------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/github/git.go b/github/git.go index 0fb3dc25..02e56814 100644 --- a/github/git.go +++ b/github/git.go @@ -4,27 +4,21 @@ import ( "fmt" "io/ioutil" + "github.com/codeEmitter/gitrob/common" + "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing" - "gopkg.in/src-d/go-git.v4/plumbing/object" - "gopkg.in/src-d/go-git.v4/utils/merkletrie" -) - -const ( - EmptyTreeCommitId = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" ) -func CloneRepository(url *string, branch *string, depth int) (*git.Repository, string, error) { - urlVal := *url - branchVal := *branch +func CloneRepository(cloneConfig *common.CloneConfiguration) (*git.Repository, string, error) { dir, err := ioutil.TempDir("", "gitrob") if err != nil { return nil, "", err } repository, err := git.PlainClone(dir, false, &git.CloneOptions{ - URL: urlVal, - Depth: depth, - ReferenceName: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branchVal)), + URL: *cloneConfig.Url, + Depth: *cloneConfig.Depth, + ReferenceName: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", *cloneConfig.Branch)), SingleBranch: true, Tags: git.NoTags, }) diff --git a/main.go b/main.go index 2fca2e0b..fc9b5d65 100644 --- a/main.go +++ b/main.go @@ -140,17 +140,17 @@ func AnalyzeRepositories(sess *core.Session) { sess.Out.Debug("[THREAD #%d][%s] Cloning repository...\n", tid, *repo.CloneURL) clone, path, err := func() (*git.Repository, string, error) { + cloneConfig := common.CloneConfiguration{ + Url: repo.CloneURL, + Branch: repo.DefaultBranch, + Depth: sess.Options.CommitDepth, + Token: &sess.GitLab.AccessToken, + } if sess.Github.AccessToken != "" { - return github.CloneRepository(repo.CloneURL, repo.DefaultBranch, *sess.Options.CommitDepth) + return github.CloneRepository(&cloneConfig) } else { userName := "oauth2" - cloneConfig := common.CloneConfiguration{ - Url: repo.CloneURL, - Branch: repo.DefaultBranch, - Depth: sess.Options.CommitDepth, - Token: &sess.GitLab.AccessToken, - Username: &userName, - } + cloneConfig.Username = &userName return gitlab.CloneRepository(&cloneConfig) } }() From 60d82f4967475e1f88a44a4356b8c593dd742e0a Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 6 Mar 2020 16:41:17 -0500 Subject: [PATCH 045/226] set gitlab repo urls --- core/signatures.go | 18 ++++++++++++------ gitlab/targets.go | 2 +- main.go | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/core/signatures.go b/core/signatures.go index 4216147a..d5251f73 100644 --- a/core/signatures.go +++ b/core/signatures.go @@ -59,10 +59,16 @@ type Finding struct { RepositoryUrl string } -func (f *Finding) setupUrls() { - f.RepositoryUrl = fmt.Sprintf("https://github.com/%s/%s", f.RepositoryOwner, f.RepositoryName) - f.FileUrl = fmt.Sprintf("%s/blob/%s/%s", f.RepositoryUrl, f.CommitHash, f.FilePath) - f.CommitUrl = fmt.Sprintf("%s/commit/%s", f.RepositoryUrl, f.CommitHash) +func (f *Finding) setupUrls(isGithubSession bool) { + if isGithubSession { + f.RepositoryUrl = fmt.Sprintf("https://github.com/%s/%s", f.RepositoryOwner, f.RepositoryName) + f.FileUrl = fmt.Sprintf("%s/blob/%s/%s", f.RepositoryUrl, f.CommitHash, f.FilePath) + f.CommitUrl = fmt.Sprintf("%s/commit/%s", f.RepositoryUrl, f.CommitHash) + } else { + f.RepositoryUrl = fmt.Sprintf("https://gitlab.com/%s/%s", f.RepositoryOwner, f.RepositoryName) + f.FileUrl = fmt.Sprintf("%s/blob/%s/%s", f.RepositoryUrl, f.CommitHash, f.FilePath) + f.CommitUrl = fmt.Sprintf("%s/commit/%s", f.RepositoryUrl, f.CommitHash) + } } func (f *Finding) generateID() { @@ -77,8 +83,8 @@ func (f *Finding) generateID() { f.Id = fmt.Sprintf("%x", h.Sum(nil)) } -func (f *Finding) Initialize() { - f.setupUrls() +func (f *Finding) Initialize(isGithubSession bool) { + f.setupUrls(isGithubSession) f.generateID() } diff --git a/gitlab/targets.go b/gitlab/targets.go index 97e3aa0f..5a826b4c 100644 --- a/gitlab/targets.go +++ b/gitlab/targets.go @@ -59,7 +59,7 @@ func getUserProjects(id int, client *gitlab.Client) ([]*common.Repository, error if project.ForkedFromProject == nil { id := int64(project.ID) p := common.Repository{ - Owner: gitlab.String(project.Owner.Name), + Owner: gitlab.String(project.Owner.Username), ID: &id, Name: gitlab.String(project.Name), FullName: gitlab.String(project.NameWithNamespace), diff --git a/main.go b/main.go index fc9b5d65..01b72ab3 100644 --- a/main.go +++ b/main.go @@ -201,7 +201,7 @@ func AnalyzeRepositories(sess *core.Session) { CommitMessage: strings.TrimSpace(commit.Message), CommitAuthor: commit.Author.String(), } - finding.Initialize() + finding.Initialize(sess.Github.AccessToken != "") sess.AddFinding(finding) sess.Out.Warn(" %s: %s\n", strings.ToUpper(changeAction), finding.Description) From 0463308fe90d2d6b50ebae54998dc7c741e04315 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 9 Mar 2020 11:21:02 -0400 Subject: [PATCH 046/226] adjust raw file GET url for gitlab sessions --- core/router.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/core/router.go b/core/router.go index 175ca23a..f0dce32a 100644 --- a/core/router.go +++ b/core/router.go @@ -15,10 +15,13 @@ import ( const ( GithubBaseUri = "https://raw.githubusercontent.com" MaximumFileSize = 102400 + GitLabBaseUri = "https://gitlab.com" CspPolicy = "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'" ReferrerPolicy = "no-referrer" ) +var IsGithub = true + type binaryFileSystem struct { fs http.FileSystem } @@ -45,6 +48,9 @@ func BinaryFileSystem(root string) *binaryFileSystem { } func NewRouter(s *Session) *gin.Engine { + + IsGithub = s.Github.AccessToken != "" + if *s.Options.Debug == true { gin.SetMode(gin.DebugMode) } else { @@ -80,7 +86,15 @@ func NewRouter(s *Session) *gin.Engine { } func fetchFile(c *gin.Context) { - fileUrl := fmt.Sprintf("%s/%s/%s/%s%s", GithubBaseUri, c.Param("owner"), c.Param("repo"), c.Param("commit"), c.Param("path")) + //Github: https://raw.githubusercontent.com/mhh/config/6257553245337bdae802a8e19935e8cdab562bad/bash/.bashrc + //GitLab: https://gitlab.com/pharrison/python-gitlab/-/raw/ab7d794251bcdbafce69b1bde0628cd3b710d784/docs/gl_objects/settings.py + fileUrl := func() string { + if IsGithub { + return fmt.Sprintf("%s/%s/%s/%s%s", GithubBaseUri, c.Param("owner"), c.Param("repo"), c.Param("commit"), c.Param("path")) + } else { + return fmt.Sprintf("%s/%s/%s/%s/%s%s", GitLabBaseUri, c.Param("owner"), c.Param("repo"), "/-/raw/", c.Param("commit"), c.Param("path")) + } + }() resp, err := http.Head(fileUrl) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ From 9081f32afe112a8935be7ec6fe2542e2e954c6d4 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 9 Mar 2020 15:54:20 -0400 Subject: [PATCH 047/226] add script to generate code for ./core/bindata.go --- build-static.sh | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 build-static.sh diff --git a/build-static.sh b/build-static.sh new file mode 100644 index 00000000..17ceea1c --- /dev/null +++ b/build-static.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +#Script to generate code for ./core/bindata.go + +#install dependencies using the directions here: https://github.com/elazarl/go-bindata-assetfs +go-bindata-assetfs -o ./core/bindata.go -pkg "core" ./static/* +go build \ No newline at end of file From 05a025e849eee1f7f49061c68cda13404e8eb4a9 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 9 Mar 2020 17:31:51 -0400 Subject: [PATCH 048/226] show proper host name on individual results --- core/bindata.go | 123 +++++++++++------------------- static/index.html | 4 +- static/javascripts/application.js | 13 +++- 3 files changed, 55 insertions(+), 85 deletions(-) diff --git a/core/bindata.go b/core/bindata.go index 89926671..9a1af005 100644 --- a/core/bindata.go +++ b/core/bindata.go @@ -1,6 +1,6 @@ -// Code generated by go-bindata. -// sources: -// static/.DS_Store +// Code generated by go-bindata. (@generated) DO NOT EDIT. + + //Package core generated by go-bindata.// sources: // static/fonts/open-iconic.eot // static/fonts/open-iconic.otf // static/fonts/open-iconic.svg @@ -10,7 +10,6 @@ // static/images/gopher_head.png // static/images/spinner.gif // static/index.html -// static/javascripts/.DS_Store // static/javascripts/application.js // static/javascripts/backbone.js // static/javascripts/bootstrap.js @@ -25,11 +24,10 @@ // static/stylesheets/bootstrap.css // static/stylesheets/highlight.css // static/stylesheets/openiconic.css -// DO NOT EDIT! - package core import ( + "github.com/elazarl/go-bindata-assetfs" "bytes" "compress/gzip" "fmt" @@ -39,14 +37,12 @@ import ( "path/filepath" "strings" "time" - - assetfs "github.com/elazarl/go-bindata-assetfs" ) func bindataRead(data []byte, name string) ([]byte, error) { gz, err := gzip.NewReader(bytes.NewBuffer(data)) if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) + return nil, fmt.Errorf("read %q: %v", name, err) } var buf bytes.Buffer @@ -54,7 +50,7 @@ func bindataRead(data []byte, name string) ([]byte, error) { clErr := gz.Close() if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) + return nil, fmt.Errorf("read %q: %v", name, err) } if clErr != nil { return nil, err @@ -75,45 +71,36 @@ type bindataFileInfo struct { modTime time.Time } +// Name return file name func (fi bindataFileInfo) Name() string { return fi.name } + +// Size return file size func (fi bindataFileInfo) Size() int64 { return fi.size } + +// Mode return file mode func (fi bindataFileInfo) Mode() os.FileMode { return fi.mode } + +// ModTime return file modify time func (fi bindataFileInfo) ModTime() time.Time { return fi.modTime } + +// IsDir return file whether a directory func (fi bindataFileInfo) IsDir() bool { - return false + return fi.mode&os.ModeDir != 0 } + +// Sys return file is sys mode func (fi bindataFileInfo) Sys() interface{} { return nil } -var _staticDs_store = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x97\x4d\x6e\xd3\x40\x18\x86\xdf\x71\xdd\x32\x6e\x84\xf0\x02\xa9\x5d\x7a\x83\xc4\x22\x42\xf9\x53\xba\x40\x48\x51\x28\x8b\xee\x90\x82\x60\x01\xa8\x1d\xd7\xa6\x31\x72\x66\x22\x7b\xd2\x50\x42\x50\x36\x1c\x02\x6e\xc0\x51\x38\x01\x07\xe1\x00\x20\xdb\x1f\xfd\x71\x9d\x0a\x16\x40\x80\x79\xa4\xe8\x71\x94\x6f\x26\xf6\x6b\x79\x3c\x1f\x00\xd6\x9f\x04\x4d\xc0\x05\xc0\x51\xd8\xde\x44\x25\x9c\x3e\x97\xb0\xc8\x4e\x36\x5f\x3e\x47\xba\x17\xab\x43\x3f\x56\x7e\xf5\x4c\x06\x83\x61\xa5\xc8\x9e\xdd\x75\xbc\x80\x82\x84\xbe\xf0\xfc\xba\x00\xfb\x02\xe0\xf6\xd7\x9c\xbc\x7a\x03\x11\x46\x10\x38\x42\x58\xae\xc5\xdd\xab\x6a\xfd\x69\x3a\xa6\xda\x4f\xfe\x38\x8e\x52\xdd\x68\x7c\x66\xd6\x9a\xbd\xbe\x71\x8d\x73\x5e\xe3\xd7\xf9\xf3\xc1\x50\x4d\x07\x5a\xe8\x49\xda\x17\xc9\xd3\xfc\x5b\x14\x84\x3e\x1d\x3f\x52\x2a\x3e\x3d\x16\xfe\xe3\x28\x9c\xee\xbb\x37\xef\x2b\xa9\x45\x24\xc3\xe4\x5c\xf9\xb3\x27\x91\x0c\xd4\xb4\xaf\x26\x32\x48\xf3\xfa\x87\x42\x0f\x7d\x91\x38\x8e\xc3\x9d\x7d\x77\x7b\x36\xeb\xb4\x5b\x75\xaf\xd9\xee\xce\xeb\xde\x6c\x67\xa7\x51\xf7\x3a\xed\xee\x7c\xee\xf0\xad\x5b\xcd\x7b\x7b\x07\xa3\x93\xd7\xb3\x37\xf3\xb7\xef\x8b\x84\x18\xa3\xa8\x6e\x94\xa2\xfb\x50\xbe\xc8\xe3\x41\x22\x63\x25\x8f\x8a\xe5\x10\x9b\x88\x20\x11\x20\xc4\x2b\xdc\xc1\x10\x1a\x23\xc4\xa5\xd0\x3e\x96\x42\xab\xe1\x25\x04\x8e\x21\x90\xe2\x10\x09\x22\x8c\x2b\x6e\xcb\xd6\x8f\x8d\xba\x32\xf4\x2c\x8b\xdf\x11\x3a\x3f\x0d\xbd\xdb\x69\xd5\xbd\x56\xa7\x75\x31\x74\xfe\xf3\xa1\x2f\xb9\xdc\x52\xfc\x35\xa4\xd0\x38\x41\x9c\xdd\x1a\x0c\x11\x22\xac\x88\xf2\x5d\x29\xca\xff\x09\xca\x98\xd7\xfe\xf4\x89\x18\x0c\x86\x95\x23\x5b\x1f\x3c\x72\x8f\xbc\x28\xcc\xe8\x77\x8b\x6c\x9f\x1b\xe3\x92\x3d\x72\x8f\xbc\x28\xcc\xa8\xce\x22\xdb\x64\x4e\x76\xc9\x1e\xb9\x47\x5e\x14\xa6\x45\x8b\x51\xf3\xc1\xe8\x9f\x19\x75\x28\xcc\x25\x7b\xe4\xde\xaf\xc9\xc6\x60\xf8\xdb\x59\x2b\xe4\x66\xef\xff\x07\xcb\xfb\x7f\x83\xc1\xf0\x0f\xc3\xec\xdd\xc1\x6e\xff\xac\x21\xb8\x84\x45\x1b\x81\x83\xef\x03\x96\x6c\x04\xa8\x36\x7b\x15\x6f\xe3\xac\xd6\x6c\x04\x0c\x86\x15\xe3\x5b\x00\x00\x00\xff\xff\xdb\x5f\x30\x2d\x04\x18\x00\x00") - -func staticDs_storeBytes() ([]byte, error) { - return bindataRead( - _staticDs_store, - "static/.DS_Store", - ) -} - -func staticDs_store() (*asset, error) { - bytes, err := staticDs_storeBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "static/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1528535411, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - var _staticFontsOpenIconicEot = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xdc\xbc\x79\x78\x1b\xd7\x7d\x28\x7a\x7e\x67\x00\x0c\x36\x62\x21\x30\x00\x37\x90\x33\x18\x02\x20\xc8\xe1\x36\x83\x85\x24\x24\x88\x26\xb5\xd0\x92\x2d\x51\xd4\x12\xd0\xb2\x15\x2e\x43\x49\xde\x24\x56\x0b\xed\x38\x72\xea\xc6\xae\xeb\xea\xc6\x31\x4d\xe7\xb2\x76\xe3\x9b\xb8\x8a\xe3\xe7\x26\x7e\xed\x50\x8a\x6c\xc7\x65\xed\xd4\xcd\x32\xb7\x89\x1d\x37\x4f\x37\x69\xfd\x6e\x7c\xed\xdc\xbc\xe4\x3e\xb9\x4d\x93\x54\xb7\x4d\xf5\x99\xe3\xf7\x9d\x33\x03\x10\xa4\xe4\xc4\xe9\x77\xbf\xfb\xc7\xa3\x34\x38\xcb\x9c\xf5\x77\xce\xf9\xed\x67\x5a\x8f\x22\x74\xfa\x4e\x84\x00\x61\x44\xfe\x30\xb2\x33\x34\x82\x00\xcd\x03\x09\x6f\x18\x35\xd3\x28\x60\x85\x08\xac\xb0\xfe\x53\x9e\x0e\x74\xd5\x9f\x1b\x5d\x8f\xa6\xd1\x31\x74\x14\x9d\x40\x08\x79\xd1\xad\x56\x8a\x84\x08\x35\xa1\xfd\x68\x06\x1d\x47\x27\xd0\xad\x34\x97\x47\xbd\xa8\xcb\x7a\x78\x84\x50\x2d\xda\x87\x8e\xa2\x93\xe8\x56\x74\x12\xdd\x81\x66\x90\x8a\x7a\x57\x3b\xf5\xa3\xfb\x10\x83\x66\xb7\x6e\xdd\x7b\xe3\xed\x3f\x87\xa7\x11\x42\xef\x91\x26\x77\xed\xe9\x96\x33\x8e\xdd\x53\x08\x41\x1d\x42\x68\x62\xfa\xce\xc9\x59\xe4\xff\xf1\x1f\x22\x04\x77\x23\x04\x43\xd3\x73\x27\x79\xb3\x91\xc9\x87\xc8\x00\x0f\xcd\x1e\xbe\xf3\xe5\x63\x3f\xe9\x47\x68\xf2\x33\x08\x79\xee\x38\x3c\x79\x62\xd6\x9c\xdf\xe4\x7d\x08\x21\xe7\xe1\x3b\x3e\x76\x68\xe4\xff\xf9\xd9\x9f\x21\x84\x97\x11\x1a\xab\x3b\x32\x33\xa9\xc2\x3f\xff\xc5\x16\x84\xf6\x5e\x44\x08\xe5\x8e\x1c\x99\x99\x74\x7c\xca\x06\x08\xed\xf3\x22\x84\x5a\x8f\xdc\x79\xf2\xee\x3f\xfe\x1c\x6a\x40\x68\x5f\x0f\x42\xcc\x83\x77\x1c\x9b\x9e\xfc\xc2\x4b\x8f\x6c\x46\xa8\xf4\x22\x42\xf0\xcd\x3b\x27\xef\x9e\x85\x27\x3d\xdf\x47\xe8\x26\xd2\x3e\x7f\x74\xf2\xce\x99\x4f\xff\x4b\xe0\x1b\x08\xdd\xf4\x14\x42\xf8\xf4\xec\xb1\x13\x27\xff\x31\xa0\x18\x08\xdd\xd2\x84\x90\x6d\x7a\xf6\xf8\xcc\xec\x0f\x6f\x67\x7e\x1f\xa1\x3b\xc9\xa0\x3f\x5e\x06\xf9\x77\x36\x9e\xfd\x2e\x09\xbf\xfb\xf7\xdd\xb7\x55\x42\x1b\x36\x60\x1e\xd9\x11\xc2\x4e\xdc\x83\x10\x9a\x35\x43\xb8\x0f\xa5\xd0\x77\xab\xd7\x73\xcd\x12\x22\x84\x46\x0f\x6d\x51\xd1\xd7\xde\x46\x6f\xbf\xc5\x10\xe8\x8c\x30\x3c\x9a\xa9\x5a\x5c\xf2\xc7\xd3\x14\x63\x3d\x4d\xd6\xbb\x0d\x88\xa1\xb1\x26\x64\xa3\x4b\x66\x43\x36\x92\x7e\xfb\xad\xf7\xdf\x47\xe8\x6d\xf4\xfe\xfb\x3c\x53\x6e\x05\x1c\xab\x0d\x02\xa6\xdb\x6b\xed\x1f\x5c\xbd\x7f\xfe\x7f\xf8\x87\xc9\xde\x40\xff\x80\x39\xc4\x20\x16\x39\x2f\x38\x6c\x80\x70\x67\x87\x12\x54\x82\x29\x25\x28\x36\x7c\xef\xc5\xcf\x7d\x0e\x73\x2b\xef\x36\xc0\x3b\x26\x84\x18\x1e\x2f\x23\x16\xf9\x90\xf3\x82\xdb\x89\x49\x59\x10\x82\x42\x56\x08\x0a\x2d\xc0\x66\x85\xac\x00\x12\x5c\x36\xbc\x30\x6f\xcc\xaa\xba\x6e\xcc\xc2\x3c\x5e\x5e\xd9\xac\xc2\x65\x75\x71\x51\x55\xaf\x6a\xa3\x66\x6d\x1b\x2c\x27\x70\x02\x97\xaa\x6a\x83\xfc\xe8\xe5\x26\x54\x55\x5d\x34\x57\x46\xa5\x6d\xf8\x91\xe3\xbc\x03\x41\x67\x47\x2d\x1f\x89\x81\xc2\xa6\x94\x6c\x26\x19\x77\x70\x99\xd7\x20\xfe\x9a\xaa\xeb\xea\xc2\x1b\x0b\x78\xf9\xb5\x47\x65\x5d\x97\x8f\x2c\x2c\x1c\x79\x74\x5d\x5d\x0f\xa9\x8b\xf8\x08\x47\x6a\x95\x1b\x80\x5e\x88\xbf\xb6\xf0\xc6\x02\xa9\x4f\x2a\x93\x8a\xa4\x81\x47\x11\xdd\x51\xe5\xf1\x13\x78\x79\x90\x1f\xd5\x0c\xba\x7d\x5e\xb7\xd3\x61\xc3\xc8\xd6\xd9\x11\x14\x38\x21\x2a\x70\x02\x6b\x85\x0c\xff\xde\xdb\x2a\x2e\xad\x3c\xab\x56\x62\x78\x59\x35\xff\x3e\x6c\x7b\xd5\xff\x48\x2b\xa4\x91\x72\x58\xdd\xd8\xbf\x67\x6c\x3a\x69\x44\xaf\xc4\xd6\x8c\xcd\x8e\xd0\xfb\x9f\xa7\xed\xd9\x91\x07\x05\x51\x04\x35\x22\xff\xa0\xb7\x31\x1a\xaa\x0d\x78\x9d\x0e\x06\xd9\x3b\x3b\x40\x8e\xb2\x89\x1c\x1b\xb5\x27\xb3\x99\x44\x84\x0b\xb3\x62\x22\xd4\x05\x29\xbb\xe0\x03\x16\xe6\xef\x98\xda\x61\xbc\x7c\xb3\xf6\x8a\xf1\x37\x0e\xac\xcd\x06\xde\x34\xce\xce\x1e\xfc\xa3\x76\xd8\x01\x27\x7b\xbf\x30\x85\x97\x0b\xef\x5c\x92\x67\xcf\xbc\xd5\xda\xfa\xec\xed\xa7\x3e\xd7\x3a\x98\x35\xbe\x18\xbc\x6b\x7f\xd3\x9e\x5b\x5d\xd6\xe9\x83\xcb\x0c\x8f\x1c\xc8\x71\xde\x46\xd6\x29\xa4\x04\x15\x0f\x28\xba\xaa\xff\xeb\x8a\xce\xf0\x86\xd7\xb8\x08\x12\x2a\x9f\x5c\x86\x67\x78\x6b\x4f\x79\x9c\x36\xb2\xa7\x6a\xf9\x48\xc0\xc1\x27\x03\x89\xa0\xe8\x02\x31\x78\x09\x6e\xb8\x74\xc9\xf8\xca\x25\x90\x74\x90\x40\xd2\x19\x9e\xa4\x2e\xc1\x0d\x0f\x1a\x17\x49\x4b\xd7\x6e\xcb\xbd\xae\x2d\x17\x90\x3d\x5e\x69\x6b\xde\xac\x69\x5c\x5c\xdb\x9a\xae\x5f\xab\xad\x9a\x75\x6d\xd1\x15\x85\xf5\x6d\xad\xb6\xa4\xeb\x3a\x1d\xd4\x6f\x9a\x1f\x90\xd3\xaa\x54\xb7\xa3\xeb\x57\x4d\x0f\xad\x9e\x9b\x12\x85\xa9\x9d\xc0\x14\xe8\x18\x12\xe6\x79\x33\x2e\xe2\x12\x81\x2e\xba\xaa\x2c\xa6\x65\x6b\xe8\xdc\xe1\x32\x99\xaf\xe1\x85\xcb\xb8\xf4\xaf\x2b\xba\x6a\xad\x15\x2e\xad\x5f\x2b\x17\x28\xba\xae\x1b\x3f\x30\xfe\xb6\x6a\xb5\x56\xcf\xdf\xba\x31\x40\x65\x0c\xcb\x64\xde\x69\xb4\xbe\x2c\x1d\x83\x0b\xaf\x1b\xc2\x32\x6d\x5f\x5f\x37\x06\x86\x96\x05\x31\x28\x06\x45\x48\x43\x37\x85\x87\x59\x03\xad\xdf\x5b\xb4\x6c\xa3\x59\x96\xcc\x67\xb5\x24\x3d\x4f\x78\x99\xe1\x3f\xe0\x3c\x81\x12\x14\x19\x25\x28\x02\x7d\x38\x11\xe6\x55\x55\x57\x55\x12\xac\x3c\xab\xd2\x03\x8f\x97\x57\x9e\x85\xcb\xc6\x2c\x48\xba\x79\x3e\xe3\x14\xa6\x6e\xc4\xa1\x16\x94\x26\xed\xb5\xf2\x4d\x91\x80\xc7\x41\xda\x0b\x45\x1d\x5c\x38\xca\x76\x81\x79\x94\x1c\x6c\x2a\x97\xcd\xa4\xec\x66\x6e\xf9\x7c\xd1\xbc\x93\x23\xa5\xd2\x48\x7e\x23\x7e\xee\xe4\xc6\xfc\x48\xa9\x64\x7c\x7e\x4b\x53\xd3\x96\x02\xec\x2b\x14\xb6\x34\x35\xe1\xd2\x48\xe9\x63\xf7\x96\x46\xf2\x9f\x19\xfb\xe2\xc9\x93\x5f\x1c\xfb\x4c\x7e\xa4\x74\xef\xc7\x4a\x9b\x47\x9a\xd2\x52\xd3\x48\x61\xdf\xd8\x40\x61\xf7\xbe\xc2\x48\x93\x94\x6e\xb2\xf6\x16\xcc\xd3\xbd\xe5\xaf\xda\xa3\x72\x24\xec\x88\x27\x33\xa1\x88\x22\xe7\x83\x29\x76\xee\xe9\xb9\xb9\xa7\xe7\xd4\x5c\x57\x67\x9e\x4c\x8c\x26\x8d\xff\x18\x0a\x19\x7f\xa5\xaa\x94\x02\x97\xf7\xa7\x1f\x45\x91\xfb\x85\xda\x00\x69\x87\xa9\xda\xa3\x71\x32\x09\x48\x46\x21\xa2\xc8\xb9\x6c\xe6\x5d\xd8\xfe\xee\xbb\xc6\xf3\xef\xc2\xfc\x69\xad\x0f\xbe\xb0\xf3\x31\xe3\x96\x9d\x93\xa7\x35\x86\x27\x99\xef\xc2\xf6\x3f\xd0\x4e\x4f\xee\x84\x2f\xf4\xdd\x6b\xdc\xd2\xa7\x9d\x9e\x44\xe5\x3e\x28\x2e\x72\xa1\x1a\xe4\x7e\x81\xae\x03\xe9\x23\xa8\x10\xc4\x06\xd6\x42\xa8\x78\xf9\xbd\xb7\xe1\xb2\xae\x93\x53\x40\xc0\xaf\xe2\x65\xc3\x4b\xf6\x4b\x19\x3f\x32\x3c\x8a\xa2\x06\x94\x44\x32\x81\x7f\x67\x4a\x68\xac\xaf\xf3\x02\x59\xcf\x5c\xb4\x19\x47\x15\x4e\x0c\x86\x1d\xa2\x10\x4f\x66\x83\x62\x56\x09\xe5\xa2\xac\x60\x23\xd4\x21\x1c\x91\x73\x1b\x21\xd3\x5a\x15\x87\x87\x83\x41\xb8\xe3\x6e\xef\x7d\x6a\xd8\xb7\xf2\x0f\xbe\xb0\x7a\xdf\x25\x5f\xdd\x3c\xf0\xc6\x3f\xb5\xc7\x62\xed\x31\xf0\xd3\x80\xe1\xc1\x07\xbf\xf7\x69\xbf\x6a\x2c\xf8\xc2\x61\x1f\x1c\x55\xa1\x3e\xf8\x89\xe7\xf4\x18\xa7\x72\xb1\xb5\x3f\x74\x3d\xca\xf4\x29\x84\x9c\x17\x82\x01\x17\x59\x8f\x90\x20\xc7\xc8\x16\xe3\xc2\xa2\x10\xcf\x06\x33\xd1\xa0\x10\x74\xe1\x2f\xb9\x54\xd5\xb5\xb2\xdf\xa5\x92\x83\xe0\x7e\x59\x7f\xc5\xe5\xc2\x5b\x5d\x2a\x21\xba\x08\xad\xa7\x93\x2e\x7a\x2e\xd7\xb5\xb3\xda\x46\x55\x03\x16\xee\x7a\xff\x49\x5a\x37\x89\xba\x91\xf3\x82\x94\x8a\x60\x32\x8e\x4c\xae\xc0\x08\x64\x73\x74\x00\x57\x1b\xa5\x08\xdf\x84\x55\x26\x9f\x0b\x65\xc5\xb8\x43\x8c\x27\xed\x22\xc7\x3a\x58\xa1\x1b\x6e\x6e\x08\x31\x76\x3b\x5c\x26\x73\x93\x3f\x5d\x17\x4f\x33\x1d\xfd\x06\x2a\x8e\xc5\xeb\x3e\x2d\x83\x03\xb8\x18\x3c\xae\x3b\x36\xf5\xc1\xab\x77\x39\xf0\x99\x5a\x3e\xd6\x1e\x7b\xd8\xf8\xe7\xb6\xbc\x52\x4c\xa7\xc7\x8a\x4a\xbe\x0d\x6a\x1e\x86\xe6\xd8\x59\xf7\xc7\x6f\xfd\xb1\x7b\x0d\x1e\xac\x31\x61\xe3\x63\x09\x6c\x90\x1c\xe1\xc2\xd1\x08\x27\x64\xf2\xb9\x6c\x26\xa4\x90\x8d\x0b\xc5\xa7\xe7\x1e\x1c\x7a\xef\xed\xa1\x07\x55\xbd\x30\x56\x60\xf8\xb9\xb1\xcf\x1e\xcb\xef\xda\x95\x3f\xf6\xd9\xb1\x95\x97\xd2\x85\x02\xb2\x38\x47\x84\x93\xb4\xbd\x00\x0a\x91\x7d\x1b\xf4\xfb\xec\x64\x4f\x85\x14\x3b\x1b\xb5\x8b\x59\x36\x95\x4f\xe5\xa3\x79\x2e\xcf\x72\xf9\x45\x19\x0e\xbe\xf3\x8e\x71\x56\xde\xb1\x63\x71\x71\xc7\x0e\x75\x71\x71\x91\xe1\xaf\x3c\xf6\xc4\x95\x2b\x56\x86\x4e\x38\x06\x84\xaa\xce\x03\x87\x62\x28\x81\xdc\x2f\xb4\x34\x47\x23\x1e\xba\x57\x05\x32\x58\xb6\x19\x38\xb2\xc5\xb2\x72\x2e\x1b\xcc\x24\x45\x81\x53\xe4\x5c\x26\x29\x32\x41\x33\x84\xcb\x63\x73\xd2\x75\xbb\x16\x8e\xac\xbc\x99\x2e\x14\xd2\x20\xa9\x24\x50\x17\x8b\xa5\x52\x91\x4c\x65\x6b\xbe\xe5\xee\xed\x47\x16\xd4\x42\x1a\xe6\xd3\x05\x32\x41\x82\xe3\x4b\xa7\x4b\xd5\xb8\xcd\x5e\xc6\x6d\x10\x54\x20\x28\x82\x44\x4e\x84\x85\xdb\x24\x0b\x5f\x12\xdc\xd6\x88\x1c\xe7\x6b\xcd\xbd\x11\xcc\x47\x83\x8a\x1c\xe1\x82\x42\xdc\x11\x8e\x28\x02\x47\x0f\x41\xbe\x08\x79\x15\x24\x55\x95\xb9\xd8\xca\x9b\x64\x2d\x71\x72\xe5\xcd\x62\x89\x71\x74\xc4\x48\x7b\xaa\x0a\x52\x8c\x5b\x79\x33\xd6\x1e\x53\x4b\x45\x9c\x0c\xc5\x3a\x1c\xd6\x38\x08\x5e\xb1\x95\x71\x77\x50\x08\xa6\x58\x98\xd7\x4d\x6e\x43\xaf\x5e\x4f\x86\xe2\x1e\x8f\x8d\xf2\x82\x16\xcb\xa3\x64\x85\x20\xdd\xa5\xa4\x38\x48\xdf\x86\xb4\xeb\xbd\x5f\xba\x18\x5e\x55\x0f\x1f\x5e\x59\x72\xb9\xaa\xe1\x1c\x45\x8d\x28\x85\xdc\x2f\x88\x4d\xf5\x75\x14\xce\xa0\xc8\x84\x21\x94\x63\x60\x1e\xe5\x8d\x90\x29\x40\x36\x93\x8b\x72\x4a\x16\x22\x8a\x20\xe7\xcd\x13\x00\x92\x9e\x2e\xbc\xe2\x8a\x71\x2b\x9b\xb9\x98\xeb\x95\x42\x5a\x37\xbc\x75\x0d\x78\xb9\xa1\xce\xec\xac\x90\x56\x5d\xbf\x20\x53\xfe\x85\x4b\x4d\x17\x54\x55\x35\x38\x97\x4b\x77\xb9\xaa\xe8\x4d\x2b\x72\x9c\xe7\x28\x9c\xe5\x08\x27\x66\x92\x62\x9c\x75\x70\xe1\x08\xc1\x23\x0e\xc2\x69\x65\xf3\xb9\xbc\x98\x55\xba\x30\x39\x22\x20\x1d\xbc\x4d\x95\xe5\xee\xfa\xa6\x7a\xe6\xb9\x73\xd1\x9b\xe0\xdd\x95\x67\x03\x1f\x09\xdd\x73\x2f\xf6\x3b\xe5\x3c\xc3\xdf\x76\x50\x96\x5b\xc4\x42\xd3\x5d\x4d\xea\xe4\x61\xf5\x1e\xef\x47\x76\xa9\x35\xdb\x36\xb4\x1c\x2c\x6e\x31\xf9\x3b\xeb\x4c\x86\x51\x13\x6a\x43\x12\xc1\x5f\xed\x69\x21\x56\xcf\x79\x28\xfd\xa0\x47\x93\xab\x60\xaf\x6c\x26\x17\x21\x0b\x29\xe7\x32\x79\x73\x41\xe9\xee\xb2\x07\x85\x60\xdc\xc6\xd8\xec\x78\x99\xcc\x8c\xcc\x3c\xfc\x47\x14\x51\x2d\x92\x34\x48\x34\xf7\x4d\x5c\xc2\xcb\xe6\xdb\x18\x87\x97\xb1\x3f\x36\x11\xa3\x85\xcc\xff\xba\x85\x64\xcc\x35\x6c\x60\x62\xa8\x06\x35\x22\xe7\x85\x90\x79\x26\xa1\x05\x22\x2c\xf8\xa1\x0b\x52\x9b\xc0\x45\x20\x13\x76\x88\xf1\x54\x91\xac\x01\xfe\x1f\x0e\xc9\x6e\x37\xfe\xc5\x5f\x1f\xdc\x95\xf3\xd6\xc0\x5d\x46\x5b\xdf\xce\x27\x8f\x6e\xdf\xb0\xf1\xfa\x1b\x99\x18\x23\xd9\x59\xe3\x0f\x6b\xbc\xb9\x5d\xc1\x7a\x3f\xb8\x8d\xb9\x9d\x7d\x47\x9f\x8c\x8b\xa7\xaf\xef\xdb\x69\xe1\xb4\xf7\xff\x02\xbf\x8e\x97\xd1\x61\xe4\x38\x2f\x7b\x80\xe2\x24\x45\x6e\x81\x7c\x17\x14\x41\x6e\x06\x07\x1b\xcd\xe7\x14\xda\xa3\x1f\x22\x51\xc5\x44\x56\x61\x36\x6a\x1e\xba\x78\x37\x38\xd8\xe4\x46\x96\x2c\x94\x83\x8d\x84\xd9\x18\x13\x66\x93\x29\x96\x20\xae\x6c\x26\xb7\x09\x92\xd9\x0e\xa0\xd1\x02\xce\xe5\x53\x5d\xe0\x6a\x0c\xf9\xbc\xf7\xc7\x9a\x1c\xad\xd7\xef\xbf\xbe\xd5\xde\xd4\x74\xbf\xc7\xcd\xc5\x02\x9e\x13\x61\x3b\x85\x8d\xec\xd8\xe9\x8f\xc4\xb8\x80\xff\xba\xc4\xd0\x86\x18\x57\xdb\x64\x03\x28\x0c\xb7\x0e\xf9\x03\x5c\x2c\xe2\xdf\xe9\x90\xed\xa4\x94\x7d\x8b\x3d\x7c\xd2\x1b\x80\xbf\xae\xe5\xd9\xa1\x48\xac\x74\x63\x5f\xfe\xc6\x52\x73\x64\x88\x8d\x71\x35\x0d\x8e\x3e\xd1\x1f\x6b\x8f\xc5\xf9\x36\x5b\x2c\xc0\xc5\x3c\xe2\x48\x52\x48\xfd\x81\xcd\xee\xb0\xdb\x3f\xc2\xc5\xa2\x41\x26\xc0\x8c\xd9\xec\xf3\x6d\x7c\x7a\x8b\xe8\x89\x71\x81\x98\xad\x8d\x8f\xc7\xb8\x70\xb3\x5f\xec\xb3\x37\xd0\x35\xc0\x78\x99\xca\x54\x4d\xc8\x79\xa1\xd6\x65\xa7\x6b\xa0\xc8\x5c\x30\xdc\x01\x90\x08\xa6\xb8\x38\x1b\x8d\xf8\x20\x99\x62\xc4\x38\x3d\x0b\xb8\x74\xc0\xe5\x3a\x60\xdc\x03\xf1\x2b\x51\xfb\x0e\xa7\x2f\xd9\xee\x3c\x7c\xc0\xe5\x7a\x1e\x2f\xbb\x56\x5e\x72\xc1\x63\x8f\x19\xde\x7b\x99\xc8\xb9\x50\x7b\xc0\x17\x82\x80\xeb\x79\x42\x14\xd8\x0a\xce\xf0\x50\xea\x44\x4e\x5d\x0b\x12\x51\x68\x30\x20\xf0\xb1\xa6\xfa\x3a\x2e\x1c\x0c\xd4\x78\xed\x88\xa5\x84\x86\x0b\x96\x89\x15\x27\x64\x81\x53\xb2\x0a\x79\x82\x4a\xd0\x6e\xc5\x09\xf1\xb1\xc8\x97\xe1\xa5\x52\x07\x0d\x18\x9e\x9c\x3d\x17\xe3\x73\x11\x01\xf2\x22\xcd\xbf\x08\x92\x4e\xc5\x12\xb6\x8a\x27\xab\x1e\x45\x68\x30\xb0\x3a\x02\xd6\x86\xc9\x18\x4c\x44\x62\x8e\x22\x54\xee\x9f\x53\xb2\xe5\xfe\x89\x54\x49\xe6\xbb\xdf\xa5\x56\xf7\xae\xd3\x43\x0e\x7f\x60\x66\x9a\xdd\x56\xf1\x0e\x75\x88\x47\x6d\xa8\x87\xf2\x0e\xe9\x84\x10\xab\xf7\x98\xbc\xa0\xdc\x02\x91\x02\xac\xce\x3a\x57\x00\x79\x13\xe4\xb0\x75\x02\x5b\xad\x30\x69\x71\x56\xf0\xc4\xf3\x4e\xdb\x41\x9b\x73\xcc\x45\x66\x5b\x2a\x3e\x4c\x52\x3f\x31\xd9\x86\x4f\x50\x6e\xeb\x17\x63\x05\x4a\xba\x5c\xcf\xbb\xc8\x9a\xb8\xe0\xab\xc5\x92\xeb\x79\x97\x71\xd1\x3c\x81\x94\x07\xab\x2d\x90\x42\x16\xad\xd7\x29\x6f\x89\x57\xf1\x2d\x30\xbc\x31\x8b\x4b\xc6\x2c\xaa\xc2\xc7\xe4\x3d\x98\x74\x01\x88\x70\x4d\xd1\x6b\x85\x5f\x58\x2d\x63\xb6\xe1\xb2\xca\x18\xb3\xa4\x95\xf5\xfd\x80\xc9\x67\x0b\x30\x0f\xf3\x44\x1e\x35\xcb\x98\x78\xb9\x05\x97\x90\x80\xd2\xa8\x17\xb9\x5f\xe8\x6a\x4f\xc6\x6b\x31\xa1\x7f\x99\x5c\xc1\x26\x47\x5a\x40\x90\x23\x7e\xf0\x01\x41\x54\xa9\x2e\xe8\x06\x93\x73\x40\x16\x70\x5a\xad\xb0\x31\xc4\xd8\x6d\x7a\x4d\x83\x3d\x09\xcb\x6e\x27\xb3\x9b\xe1\x3c\xc6\x59\x37\xc7\xf8\x72\x35\xee\x45\xb0\x03\xd7\x0c\xbb\x4d\x90\x5d\x4f\x03\x9c\xac\xe5\x83\x9e\x11\xbf\xfb\x8a\xdb\xef\x77\xa7\x9f\x4d\xd7\x43\xb3\xb1\xa7\x0a\x6f\x55\xe9\x1f\x1c\xc8\x83\x9c\x17\x5c\xac\xa3\x4c\x73\x82\x22\x0b\x42\x30\x25\xc0\x65\x93\xa6\xc1\x65\xd5\x98\xc5\xcb\xaa\x71\x51\x05\xc9\xf0\xaa\x74\xee\x43\x4c\x00\x2f\xa3\x9a\x8a\x4c\x14\x85\x2e\xdc\x0d\xf9\x48\xb4\x88\xf1\xef\x3d\x62\x84\xf8\xeb\x6f\xaa\x1b\x79\x78\xc7\xce\xd4\xa7\x4f\xe2\xe5\x87\x8d\x50\x7c\xfb\x78\xfd\xb6\x87\x6f\xd8\x9e\xfa\xf4\x49\x5a\x5f\xa6\xb0\xb3\x57\xe4\xa4\x68\x3e\xea\x82\xc5\x2b\x57\x16\x09\x80\x71\x89\x46\xc8\x6a\x40\x85\x37\xa9\x94\x85\x28\x1b\x65\x01\xe6\xcd\xd2\x0c\x4f\x42\xba\x70\x6b\xcb\xd2\x71\x85\x5c\x90\xca\xa7\x16\xc9\xda\x2d\x5e\xb9\x62\x2e\xdf\xe2\x95\x2b\x68\xdd\x18\x4c\x99\x0a\xd8\x14\x9b\x22\x4b\x48\x5a\x24\x8b\x48\xc2\x75\x72\x67\x0d\x72\x5e\xf0\xae\x93\x3b\xd9\x14\x1b\xad\x88\xaf\xb8\x74\x45\x1d\x79\x0e\x86\x2a\x72\x67\x9f\x99\x5e\xd7\x4e\xe8\xea\x76\xf2\x6c\x94\x8d\xe6\xa3\xf9\x54\x3e\xc5\x96\x5b\xbb\x32\xb2\xb8\x58\xfe\xbf\xda\x62\x55\x66\x45\xbe\xa6\xb8\x20\x88\x12\xc8\x79\x21\x56\xeb\xb2\xf8\xc1\x98\x49\xff\x05\x82\xc5\x21\x9b\xb1\x2b\x9c\x90\x55\xaa\x49\x63\x26\x47\x76\xcc\x0e\x8f\xcf\xb8\xe8\xf3\xec\x30\xfe\x71\x07\x5c\xde\xe1\xf1\xf9\x3c\x2b\x73\x1e\x9f\x8f\xe1\x63\x9c\xec\xf3\xec\xd8\xe1\xf1\xc9\xdc\x36\x5d\xb7\xb2\x3d\xf8\x8c\xc7\x87\xd6\xf0\x77\x44\xde\xa9\x43\xee\x17\xb8\x80\x77\xad\xbc\x53\x3e\xed\x09\x85\x8b\x44\xd9\x64\x37\x94\x05\x1e\xec\xfd\x95\xa6\xfd\x4a\x33\xce\xaa\xf1\xe4\x48\x6b\x4f\xa0\x4a\xe2\x21\x2f\x6e\xb8\xd0\x2a\x8c\xb4\xf5\x78\xab\x60\x56\x8f\x24\xe4\xbc\x90\x6c\xa6\x73\x03\x32\x2d\x42\xc8\x58\x31\x6b\xea\xac\x04\x42\xaa\x94\x22\x84\x0a\x20\x13\xb2\x17\xc3\x0a\x9b\x52\x36\x42\x26\x07\xaf\x1e\x59\xe8\xe8\x77\xbd\x52\x3a\x5d\x32\x7e\xde\x34\x37\x16\x7c\x64\x3b\x63\x03\x07\xe6\x62\x8b\xba\xbe\x18\x66\xf8\x85\x23\x72\x60\x5f\x6f\x43\x9d\x4c\xf8\x4a\xb9\x57\x19\x9b\xdb\x7f\xdc\x98\x85\x18\x07\xa4\x00\xf6\xc7\xd6\xac\x5d\x18\xb5\x20\xe7\x85\x06\x6e\xfd\x38\x52\xac\x58\x1e\x42\x54\xe4\xc8\xb1\xdd\x08\xa2\xd5\xf7\xc3\xba\xfe\x16\xed\xf9\xa6\x2b\x3a\x61\x1a\x56\xfb\xd4\x75\xab\xbf\xe2\x15\x99\x52\xd1\x35\xf2\x4a\x6d\x59\x5e\xa9\xf4\x43\xd7\x8d\xf6\x62\x36\x5d\x2a\x1a\xc5\xb1\x39\xd2\x34\x5e\x36\xdb\x2c\x96\xe6\x9e\x9e\xdb\x7f\x7c\xcd\x98\x3d\xa8\x19\x39\x2f\x70\x5e\xca\x93\x84\xe4\x68\xdc\xc1\x3a\x88\x24\x9d\xb3\x5f\xdd\xee\x95\x8f\xdf\x78\xdb\x52\xfb\x40\x7b\xfd\x02\xf4\xac\xed\x81\xe1\xd5\x13\x07\x03\x2d\x99\xdc\x91\x05\x7d\x7d\x5f\x4c\x65\xcc\x0c\x72\x21\x3f\x72\xbf\x50\xe3\x26\xe4\x87\xf2\xa0\x20\x32\x0a\x1b\x15\x53\x09\x25\xca\x8a\x79\x82\x4f\x2e\x5a\xea\x35\x5c\x32\x03\xaa\x1e\xd0\xcd\xbf\xaa\x71\xb7\xa0\x14\x72\x5e\x68\xe5\x03\x26\x1d\x6f\x81\x7c\x94\x6d\x01\xce\x0f\x51\x36\xe5\x07\xb1\x1b\xd8\x54\xbe\x1b\xb2\x9b\x80\x08\x27\x65\x1e\x0f\x0e\xaa\x72\xd3\x5d\x23\xb2\xf7\xae\xbb\xbc\xf2\xc8\x5d\x4d\xf2\xda\xe4\xd9\xd3\xa5\xd2\xe9\x12\xc3\x5f\xf3\x65\x39\x79\x7f\x89\x94\x5a\xb3\xc7\x19\xe4\x40\x6e\xe4\x7e\xc1\xc5\x3a\xcc\x79\x99\x6a\x44\xd6\x5e\xd6\x20\xc2\xbc\x6e\x52\x11\x42\x3a\x89\x04\x54\x5d\x97\x35\xeb\x3a\xcb\x75\x2d\x29\x9d\x09\xa6\x08\x10\x74\xaa\x6e\x21\x35\x4d\xea\xa1\x5f\xa3\xae\x7b\x7d\x5d\x88\xb2\x44\xb2\x57\x55\x63\x56\xaf\xaa\x6d\xea\xdc\xae\x35\x6e\xfb\xea\xb8\xed\x51\xaa\xfd\x34\x69\x9e\x6e\xcc\x5a\xf5\xc9\xb0\x75\xe4\x30\xf1\x09\x5e\x46\x29\xd4\x83\xfa\xd1\x46\xb4\x15\xed\x42\xc1\x41\xdf\xf6\x6d\x43\xc5\xc2\x40\xb6\x57\x6a\x0b\x23\x07\x55\x8e\xc4\x40\xc9\x66\x72\x96\x1c\xac\x98\xd4\x6a\x23\x98\xdb\x9f\xe2\x1c\x31\x9e\xcc\xd0\x85\x21\xf8\x87\xaa\x0c\x88\x68\x47\x38\x0f\xd6\x5c\xae\x8d\x20\x70\x26\x43\x20\x96\x4e\x97\x54\x02\xf7\xa2\x4c\x4e\x62\x75\xe2\xcb\xed\xe4\x68\xc0\x89\xf6\x98\xcc\xc5\x8c\x8b\xea\x15\x8e\x12\x3b\x89\xd0\x32\x0e\x2f\x5f\xa3\x86\x99\xf0\xc6\x68\x8d\x18\x27\xc7\xda\xbf\xac\xaa\x3a\x2d\x2f\xcb\xb4\xb6\x45\x2f\x28\x8c\xdc\xc8\x71\xde\x01\x84\xce\x67\x14\xc2\xb0\xa5\x84\x78\x96\x30\x5e\xfa\xca\x92\x8b\x89\xba\xdc\xef\x5d\xd2\x5d\xd5\xbc\x0f\x4b\x39\xae\x46\xc2\xfb\xd4\x85\x6b\x29\xd2\xb3\x5d\x8d\xf4\x52\x8c\x3d\x54\xde\x94\xeb\xf0\x1e\x61\xe5\xd4\x8f\x59\x5a\x91\xb5\x98\x8f\x92\x59\xd9\xa4\xd2\x57\xd3\x1e\xd7\x7a\x9d\x2e\x81\x5c\xb5\x16\xa9\x4a\x77\xb4\xf2\xac\xf6\x2b\xcd\xd4\x5f\x57\xed\x25\x0f\xd5\x4d\xf9\x07\xbd\xe1\x40\x59\x87\x67\xbf\x5a\xbf\x5e\xe6\x3c\xe8\x0e\xb9\x6c\x78\x2b\x7a\xf6\xbf\xb7\x06\x5d\x66\x09\xad\x81\xda\x2a\xe7\xdf\x65\xf1\xa2\x35\x83\x6e\xc2\x87\xfa\xdc\x76\x2a\x93\x09\x65\x42\xc4\x0a\x65\xea\x93\xb7\xf8\xcf\x7a\xfc\x33\x8f\xef\xbd\xb7\x09\xc1\xf1\x79\x56\x6a\x3d\x3e\x13\x25\x98\x94\x47\x37\xfe\x90\xd0\x9c\x3f\xb7\x94\xf1\xab\xfb\xba\x16\x71\x94\xe6\x44\x23\xe1\x90\xc7\xd4\x55\x70\x42\x3e\xca\x06\x15\x4e\xe4\xc4\xac\x10\x14\xb3\x4a\x34\x08\x51\x10\x54\x15\x5e\x55\xe5\x0a\x67\x0b\x07\x65\xe3\x2c\x1c\x24\x53\x90\x55\xa3\x48\x72\x2e\xab\x2a\xc9\x92\x8d\xb3\x68\x0d\xbc\x4c\x3d\x9e\x84\x32\x04\x5e\xbd\x9d\xc9\xc6\x30\x5d\x6b\xfb\xd5\x6b\x5d\x86\x17\x9b\xc9\xe5\x0b\x98\x88\x61\x66\x46\x36\x9f\x4a\x96\xdf\xad\xa7\x7d\xaf\x98\xc2\x66\xd8\xc7\x30\xce\x88\xef\x40\x7d\x9a\xf0\xb9\x8e\x9b\x02\x16\x13\xb7\x7e\x63\x50\x48\xdf\xe5\x8f\xd9\x02\x37\x39\x08\xbb\x9b\xae\x3f\xe0\xdb\x7a\xf5\x3e\x21\xa7\x9d\xf0\x73\x36\x13\x67\x52\xfd\x33\x08\x9c\x00\x12\x91\x22\x8c\x8b\xba\x71\xd1\x3c\xec\xa6\x3e\xdc\x98\x55\xd7\xea\xe8\x19\x5a\x9f\x5d\xd5\x41\xd8\x4d\x9d\xb0\x89\x2b\x48\x2b\x3a\x01\x9d\xa5\x4d\x5f\xd5\xd3\xd9\x50\x80\xf4\x6b\xc7\xa6\x9e\x2e\x28\x30\x89\x2a\x86\xc6\xb4\xaf\xc0\xc1\x2a\xd6\x85\x22\x7d\xe9\xad\xb5\xcc\xcc\x6a\x7b\x1e\x54\x47\xe4\x68\x2f\x63\xed\x77\x4e\xa4\x26\x2d\x31\x93\x88\xe6\x29\xc5\xa2\xc2\x29\x81\xa9\x4a\x0e\x14\xec\x2b\x14\x8a\xbb\xe6\x9e\x9e\xdb\x85\x97\xdf\xfd\xe2\x69\x4d\x3b\xfd\xc5\x86\x52\x29\x74\xeb\x30\xa1\x5d\xc3\xb7\x56\xd6\x95\xf2\x49\x76\xe4\x44\x5e\x14\x40\x61\xb2\xae\xb5\x41\x5f\x8d\x9b\x60\x75\xf3\x1c\x90\x7f\x2e\x10\x12\x04\x45\xd1\x47\xc8\x82\x04\xf3\x2b\x9b\xc9\xe4\xc9\xac\xc9\x46\x22\xd2\x01\xe1\x25\x4d\xbd\x0f\x39\x09\xba\x69\x2b\x22\x72\xf9\x93\x70\x19\x2f\xa3\x02\x72\x9c\x6f\x36\x6d\x00\x9c\x22\x47\x23\x31\x10\xb3\xc9\x0e\x30\xb5\xa6\x51\x7b\x33\xc4\x80\x62\x4d\x31\x2b\xc6\x53\xc9\x8d\xa0\x70\x44\x6c\xca\x6d\x84\x4c\x32\x95\xe8\x02\xc2\xb9\x14\x40\x57\x77\xf4\xf2\x92\xea\x0c\x5e\xf1\xf8\xa2\x1e\x80\xde\x0d\xdb\x95\x1d\x6b\xf2\x8c\xf7\x69\x1e\x5e\x56\x23\x2d\xdd\xea\x41\x87\xcf\x23\x7b\x9a\x99\xeb\xbc\x3b\x15\x59\xd9\xae\x5e\x2b\xb3\x0a\xc6\xa6\xad\x93\xb5\x31\xe6\x5a\x07\x5d\x20\x04\x81\xcc\xd2\xb4\x2c\xe0\x65\x32\x5f\x5c\x22\xbf\x6b\xd6\xc6\xac\x67\x37\x6d\xa4\x10\x14\x82\xf4\xa1\xe6\x1f\x52\x8b\xac\x2b\x48\xe4\xb7\xaa\x8e\x1b\x71\xc8\x79\x21\xe8\xa1\x72\x06\x70\x71\x07\xa7\x04\x85\x60\x26\x27\xac\x46\x41\xda\x73\x4a\x37\x2e\x6a\x78\xd6\x0a\x97\xd5\x53\x7b\xc8\x36\x3b\xad\x55\x22\x6b\xc6\x61\xb6\xe9\xf7\xd8\xac\xf1\x87\x1d\x62\x56\xce\x65\x45\xa8\x8a\x4a\xda\xe9\x3d\xa7\x74\x32\x21\x1a\x21\x73\xa2\xcd\x91\xe1\xd2\x08\xd5\xf1\x5e\xa2\x3a\xac\x5a\x14\x47\xce\x0b\x75\x21\x93\xb7\x8b\x36\xdb\xac\x6d\x56\x64\xf2\x38\xee\xe0\xc2\x11\x53\x2f\x49\xb2\x40\x8a\xd7\xb4\x8d\x6f\xc9\x6b\xbf\xd2\xf2\x5b\xc6\xdb\x6a\x3e\xda\x1e\x9b\x1b\x23\xa4\x26\x5d\x60\xf8\x78\x4d\xc7\xf1\x63\x0b\x1b\xc9\x2e\xdc\xb8\x70\xec\x78\x47\x8d\xb1\x3d\xc6\x8d\xcd\xc5\xda\x63\x85\x34\x57\x75\xd6\x30\x72\x10\x38\x32\x14\x8e\x2e\xa0\x98\xd7\x94\x21\xad\x23\xea\x55\xcb\xfa\x41\x3a\x3e\x4c\x76\xee\x9a\xf2\x60\x42\x9a\xac\x11\x81\xbd\x4e\x16\x8a\x41\x08\x24\x86\x87\xcb\x16\xbe\x5f\xc3\x3b\x70\x62\xc2\x7a\x74\x9d\x1c\x66\xfa\x98\xac\x17\x5a\x03\x5b\x1b\x72\x21\xe7\x05\xa7\xdd\xda\x1b\x9c\x3d\xc1\xd9\x13\x41\xc1\xdc\xf7\x96\xa0\xbb\x4c\x19\x06\xdd\x98\xad\xc2\xcb\x36\xe4\xa6\xfc\x9f\xcf\xe3\xb4\x9b\xfc\x9f\x3d\x28\x04\x13\x76\xce\x9e\xc8\xda\x05\xce\x94\xb0\x08\x2a\xb1\x4c\x86\x5e\x53\x15\xaa\x93\x49\x94\x16\x97\x17\x17\x97\x93\x6a\xd5\x59\xa2\x63\x11\x57\x75\x8b\x51\x36\x19\x77\xb0\x02\x2b\x84\xa3\x02\x2b\x46\x14\x39\xcf\x11\x89\x39\x25\xe4\x95\x64\x56\xcc\x2b\x45\xc0\xa5\xe9\x9b\x03\x3b\x5f\x7d\xa2\x05\x76\x04\x8c\x73\x21\x48\x07\xde\xba\xe9\x81\xe3\xfb\x0f\xde\x76\xc7\xf3\xb9\xf7\x51\xe8\x1b\x01\xbd\xfe\x95\xf8\x45\xbc\x2c\xdf\xd6\x77\x6c\x5c\xed\xcc\xab\xea\xce\x7b\xfa\xee\x3c\xa0\x0e\xb6\xaa\xf7\xff\x29\xe9\xd7\x71\x95\xfd\x98\x50\xb4\xe0\xa0\x2f\x12\xae\xad\xd0\x4c\xc7\x3a\x9a\xa9\x70\x62\xde\x7a\xf0\xf2\xca\xe6\x32\xcd\x2c\xe3\x86\x8a\x41\xf9\x2a\x3a\xf6\xdb\xf0\x95\xaa\x4a\x96\xe9\xc3\xf1\x86\x26\x5f\xf9\x9b\x78\xc3\x0f\xe6\x4b\x4d\xde\xf2\xd7\xf1\xa5\x1f\x9e\xaf\xd4\x75\x7d\xd5\xe7\x81\xe1\x91\x8b\xd2\x0a\x9f\xdb\x49\xf7\x96\xc2\x89\x41\x21\xab\x70\x02\x08\xc1\x14\x9b\xca\xeb\x44\x2c\xa0\xc4\x66\x7e\xf1\x8a\x7a\x85\xe1\xd5\x95\x67\xa9\x39\x72\x96\x24\xab\x65\x8d\x46\x24\xa2\x2c\x72\xbf\xd0\xdb\xca\x37\xd5\xd0\xbd\x26\x37\xdb\x5a\xc0\x67\x13\xe3\x5d\xb8\x1b\x8a\x8e\x32\xfb\x95\x92\xa3\xe6\xf1\xcd\x47\x2c\xde\x14\x1e\x93\xa7\x6f\xd8\xd2\xdb\xea\x72\x39\xa2\xbb\xf6\x3f\xb8\x65\xdb\x1f\xdc\xbc\x2d\x18\x64\xbc\xe9\xae\x91\x1b\xd5\xfb\xa9\xe2\x49\x77\xd7\x36\xc5\xb8\x66\x1f\xa5\xb6\x78\x59\xc8\x0e\x0e\x66\x83\x01\x6f\xfa\xf0\xae\xeb\x8a\xa5\x52\x4b\x8b\x23\x52\xec\xdf\x22\xc7\x55\x53\x03\xe5\xf0\x35\x73\xb1\xa6\x5a\x37\xa5\xe2\x96\xdd\x28\x8e\xff\x12\x09\x54\x26\x6f\x8d\xd7\x33\x04\xa7\x14\xc0\x54\xd2\xe6\xe4\x68\x24\xec\x87\x68\xc4\xd2\xc5\x12\xc4\x9b\x22\x04\x2d\x0a\x51\x80\x1d\x0c\xc3\xb0\x11\xff\xc1\xa1\x8e\x4f\x74\x48\xd2\x75\xb7\xd4\xc6\xb8\x7a\x7f\xd1\xb8\xdf\xf8\x67\xb8\x6b\x53\x80\xfb\xa4\xf1\xad\xfb\x60\x03\x5e\x86\xe0\x2d\xd7\x75\x74\x48\xa7\xa5\xa1\x83\xbe\x3a\x2e\x16\xbe\xce\xb8\x1f\x02\x70\xef\x26\x5f\xc4\x1f\xfb\x81\xf1\x8d\x07\x60\x43\x95\x6e\x80\xd0\x3c\xe7\x05\x42\xe6\x70\x15\x99\xab\x26\x6d\xab\x24\xad\x5a\xa7\xd0\x40\xe5\x6e\xb1\xb1\x86\xd2\x75\x82\x12\x7d\xb6\x68\x97\x2d\x9b\x29\xda\x32\x40\x31\xa4\x28\x90\xb8\xee\x6c\xdc\x92\xed\xe8\xd8\x78\x5d\xa7\xa7\x9e\x46\x85\xde\xfe\x5e\x1e\xea\x68\x7c\x53\xc2\xb8\x48\xd3\x0c\x6f\x0b\xf4\x0d\x1e\xe9\xeb\xba\x65\xdb\xd8\xd6\x9b\xbb\xcc\x44\xdb\xe8\xe0\xd0\x75\x43\x3b\x8d\x15\x33\x79\x68\xca\xca\x58\xc7\x97\x78\x09\x7f\x61\xf1\x25\x4a\x50\x0c\x09\x9c\xc0\x46\x85\xac\xa8\xaa\xa6\x27\x43\x95\x1c\xf3\xed\x6f\xa8\xd5\x7a\x3c\x77\xd9\x3f\x26\xa4\xb0\x0a\xa3\x00\xa3\xe4\xc5\xc5\x45\x55\x5f\x5c\x34\x2e\xaa\x0f\x0e\x11\xf4\x63\xf2\x33\xeb\xfc\x15\xec\xd4\x1e\xe3\x2e\xc3\x8c\xb3\x14\xa9\x26\xfb\x0a\x12\x5c\x26\x9d\x99\xc6\x01\xca\x11\x1b\x45\x42\x01\x2a\x3a\x44\x0a\xbf\x1e\xe4\x38\x9f\xa0\xbe\x39\x54\x17\xcf\x72\x39\x93\xcc\x77\x01\x95\x9c\x68\x16\x61\x04\xe2\xec\x2a\x57\x93\xcf\x92\xb7\xa5\xd3\xa5\x7c\x67\x63\x54\xe7\x62\x9d\x79\x1a\x2f\x15\xf5\xda\x40\xb2\x8b\xc6\x3b\xf3\x0c\x5f\x2a\xf6\xde\xe8\x9f\x73\xc5\xb8\x82\xff\xc6\xde\x62\x89\x26\x0b\xc5\x52\xa0\x76\x44\xb2\x52\x6f\x92\x17\x68\x1d\x7f\x48\xe4\x08\x37\x6b\xcd\x89\x8d\xb2\x29\x56\x88\xe6\x83\x42\x3e\x05\xf3\x8b\x8b\xea\xe2\x22\x5c\x5e\x5c\x34\x66\x17\x17\x19\x9e\x26\xcd\xc4\x35\xda\xf0\xb0\x94\x36\x84\xac\xca\x66\x53\xaa\x59\x9c\x1e\x5e\x52\x9b\x34\x42\x32\x8c\x8b\x34\xb9\x5e\x6f\xb4\x15\xed\x43\xee\x17\x76\x6f\x4b\xad\xb7\x93\xb3\xd1\x88\x83\x75\xc4\x1d\xd1\x88\xa3\x0b\x58\x47\x33\xb0\xa9\x38\x1b\x89\x36\x43\xb4\x19\x58\x42\x87\xf3\xb9\x24\x39\xe5\xf9\x22\x93\x4a\x16\x21\x9b\x49\xa6\x92\x45\x26\x9f\x8c\xfa\x20\x1a\xc9\x45\x9b\x99\x4d\x90\x4d\xae\xfa\x82\x8c\x5e\x1f\x89\xd4\x44\xdc\x1d\x41\xbb\xc3\xed\x8b\x3a\x42\xf5\x3c\x5b\x70\x3b\xa0\xd6\xb5\xb3\x26\x91\x0b\x0c\xf4\xec\xb8\x3f\x8d\xa3\xb1\xe6\x9e\xde\x46\x80\x96\xe6\x60\x4d\x8d\x3d\xbc\x19\x80\xf1\x38\x42\x4c\xff\xab\x62\xbf\x9d\xc9\x39\x19\x17\x1b\x70\x45\x02\xab\x0e\x25\x52\xc8\x2f\xba\x6c\xbe\x68\xd8\xe9\x72\xda\x6a\x87\xb7\x42\xdc\xdd\x9c\x63\x5c\x35\x91\x5b\xf9\xc8\xf0\x40\xb8\x8e\x0d\xa5\x9b\xfd\x92\xd0\xe6\xa9\xad\xa9\x77\x73\x51\x87\xdd\x53\xc3\xd6\xe2\xd0\x9d\xee\x16\xaf\x13\x1c\x4c\xb8\xc6\xe1\x89\x30\xc9\xb5\x32\x9e\x67\xd5\xd7\x06\xa2\x90\x62\x53\x44\x80\xe1\x04\xfc\xca\xa4\x31\xab\xea\x2a\x48\x6a\x85\x16\x18\xb3\xea\xb7\x55\xe8\x56\x29\x07\x1f\xa8\xc8\xec\xd5\x34\xcb\xb4\x4b\xa4\x50\x07\xea\x46\x0a\xca\xa3\x02\xda\x84\x12\x83\xf1\xe2\x86\x81\xbe\x5c\x46\xee\xe9\x92\xda\xdb\x92\xad\x71\xbe\xb9\xa9\xa1\xae\x9a\xb6\x05\x4c\xa4\x9c\xaf\x7a\xec\xbf\x65\xba\x4c\xf7\x74\xd3\x07\xe4\x43\x24\xd6\x50\xc8\x6b\xfd\x21\xd7\x55\x72\xec\xea\x1c\xa3\x83\xe1\xab\xe7\xe1\x5a\xcb\xf0\xd8\x3f\x20\xbe\xca\x08\xad\x3c\x7b\xed\x38\xa5\x60\x94\xd5\xa9\xfc\xa2\x6a\x7d\xc0\x07\xf9\x99\x05\x09\x49\x13\xec\x56\xb8\xca\x58\x9b\x8e\x18\x66\x9c\xe0\x5b\x12\xea\x66\x50\xb1\xc7\x2f\x53\x7b\x7c\x3d\x12\x90\xfb\x85\xe6\x06\xce\xe7\xa0\x32\x2e\x35\x27\x95\x95\xb7\x41\x6a\xc1\x35\x0d\x2d\x65\x41\x34\x84\x17\x43\x31\x6e\xe5\x59\x2e\x56\xd7\x80\x4b\x0d\x75\xa1\x95\xdb\x42\xb8\x60\x09\x94\x21\xe3\x02\x35\x6d\xee\x08\x19\x9c\xcb\x65\xbc\x15\x0a\x5d\xb0\x04\x48\xa8\xec\x1f\x05\x39\xce\x37\x98\x76\x13\x4e\xa4\x9a\xd5\x6c\x26\xd9\x01\x59\x9a\xe2\x82\xe1\x48\x01\xa8\x16\xd9\xd2\xdc\x94\x53\xd9\x20\x29\x05\x92\xcc\xc5\x40\x8a\x71\x32\x8d\xc5\x38\xd9\xb8\x48\x55\x36\xe5\x58\x8c\x93\xf1\xb2\x1a\xe3\x16\x17\xb9\x98\xaa\xc6\x38\x63\xd6\x0c\xcb\x69\x98\xe7\x62\x15\xfc\x49\x69\x7c\xa2\xe2\x9f\x58\xd1\x67\x53\x74\x49\x00\x90\xa4\x23\xa2\x29\x53\x5e\xca\x66\x20\xfc\x2b\x4d\xb6\x3c\x49\x4e\x3d\x73\xca\xf2\x28\x91\xf1\xb2\x76\x5a\x8d\x71\x3a\x9d\xfe\xc1\x3d\xa7\x4e\xed\x31\xce\x92\xb8\xce\xc5\xd4\xd3\x56\x7f\x21\xda\x5f\x0d\x72\x9c\xf7\x62\xe8\xec\x08\x50\xf4\x4c\xb5\x5b\xac\x0b\x4c\xcf\x9f\x82\xf1\x65\xe3\xcb\x70\xf6\xe9\xb9\xb9\xb1\xb1\xb9\xb9\xa7\x0b\xc6\x73\xb0\xb7\x6a\xac\xee\x55\x9f\xab\x54\x50\xcc\x8a\x9c\x18\x14\x09\x26\x24\x5b\x46\x25\x92\x07\x63\xdc\xa9\xeb\x30\xbf\x96\xae\xd5\x54\xcb\xdb\x41\x21\x14\xcc\x83\x92\x4d\xe5\xa3\x41\x93\x81\x24\xd5\xcb\x5e\x54\xc6\x45\x95\xe2\x01\x15\xa4\xb5\xfa\xdd\x10\x91\x83\xbc\x76\xcb\x47\x66\xd5\xe4\x18\x54\xa2\x4a\x5e\x09\x86\xf0\x95\x50\x68\xc5\x11\x32\x65\x55\x86\x27\x71\x92\xa7\x9b\x26\x1e\x73\xcf\x99\xf6\x2c\x0e\xf1\xa8\x03\xb9\x5f\x48\x0a\x0d\x11\x37\xdd\x73\x72\x24\x5a\x04\xaa\xdc\x23\x8c\xb6\x0f\x44\xc2\x3f\x45\xa9\x25\x5d\xce\xe5\xbb\x40\x88\x3b\xd8\x66\xaa\xd7\x4b\xea\xf9\x83\x8a\x72\x30\x7f\xcb\xed\xb7\xdf\x42\x62\x1f\xcd\x91\x98\xd2\xdb\xab\xc4\x36\xb5\xb7\x6f\x82\x13\x34\x88\x29\xbd\xbd\xb8\x34\x38\x30\x30\x78\xf7\x53\x77\x5b\x81\xba\x61\x74\x43\xa6\xa7\x27\x43\x9e\x0d\xa3\x1b\xd6\xf8\x63\x11\xfe\x29\x62\xe2\xc4\xb2\x61\xcc\x41\x09\x27\x55\x1c\x66\x95\xb0\x69\xb3\xa6\x44\x80\x6a\x08\x80\xa1\x26\xc3\xd9\xd3\x25\xd5\x74\xc6\xd8\xf4\x11\x59\x26\x31\x86\xa7\x76\xc2\x57\xc6\x37\x26\xbe\xe5\x23\xcb\x5f\x2c\xed\x1f\x4c\xfc\xce\x4d\x6e\x1a\x5f\x4b\xb7\x03\xc8\x71\x9e\x35\xfd\x41\x38\x91\x61\xa9\xe9\xa4\x88\x37\x81\xa8\xc3\xe5\x7b\x2e\x39\xce\x1a\xde\x7b\xe2\x3b\x27\x1a\xd8\x2f\x10\xfe\xf4\x2b\x5e\x55\x2d\x7d\xfd\x9f\xae\xf3\xfe\xb6\xbe\xac\x65\xff\xd3\xf2\xb3\xde\x33\xf6\xdf\xe3\x17\xbb\xca\xb1\xff\xf6\x7e\xb1\x1f\x34\xae\xf5\x5e\xb1\xab\x7b\x2f\x88\xea\xc9\xfa\xd4\x7a\x2a\xeb\x23\xc6\x53\x2c\x27\x72\x42\x16\x92\xd9\x8a\xbd\x17\x5e\x7e\x63\x61\xe1\x08\xef\x67\x08\x9e\xeb\x66\x60\x9f\x65\xd5\x5d\x78\x63\x81\x61\x54\x5d\x87\x6e\x3f\x7f\xa4\xab\x6c\xc6\xad\xd8\x27\x4b\xd4\xce\xec\xbc\xd0\x54\x6f\xf2\x9f\x96\x6b\x87\xc2\x95\xfd\x59\x94\x20\x61\x9a\x83\xa6\x4d\xfd\x41\xc6\x66\xa3\x7e\x56\xaa\xc9\x94\xa9\xe1\x51\x95\x48\x13\x31\xce\x38\x4b\x9d\x3a\x16\xe1\x20\xf6\xc7\x26\xca\xbb\x7e\xad\x2c\x64\xda\x2d\xbc\xee\x8a\x1c\x16\x14\x12\x4a\x50\xc8\x0a\x79\xfa\x0b\xf3\xc6\x2c\xb5\x85\x82\xa4\xd3\x5f\xc2\x2f\xeb\xc6\xac\xaa\x5a\x3a\x34\x1b\x42\xef\x7f\x16\xde\xc3\x7f\x89\x82\x48\x44\x1b\xd1\x28\x81\xef\x8d\x83\x72\xa2\x29\xe4\x36\xfd\xea\x4c\x77\x0c\xd6\x6e\x39\x59\xe4\x85\x0f\xcc\xf1\x5b\x56\x99\x78\xb2\xc3\xe4\x14\xf3\x51\xd3\x64\x4c\xb1\x1d\xc9\x87\xdd\x6e\x0f\x17\x0b\x78\x8c\x8b\xa6\x87\x05\xcc\x9b\xfe\x18\xc6\xec\xda\xf4\x6d\xb5\xf5\xcd\x1c\x38\x40\x0f\xc5\xfc\x1e\x5f\x63\x88\xb1\xd9\x4d\x27\x3b\xea\x79\x86\x9f\x37\x3d\x2f\x16\x4d\x7f\x0a\x33\xa1\x57\x27\xfa\x98\x58\x2d\xd7\x0c\x10\x0b\x7b\x1b\x1c\x97\xa9\x77\x1a\x34\x5b\x7b\xa9\x99\xf2\xc8\x29\xb4\xdd\x9c\xeb\xae\x1d\x5b\x3a\x03\x4e\xba\x97\x42\x0a\x27\x66\x95\x84\x92\x8b\x46\xa8\xa0\x63\xce\x6f\x13\xe4\x92\xa9\xa4\xa3\x2a\x09\x39\xcb\x5e\x47\xca\x44\xa2\x91\xdc\x26\x0b\x4a\x7e\x70\xb0\x8e\x64\x2a\x99\xc9\xdb\xa9\x42\xf7\x5b\x84\xfb\xb9\x1f\xe2\x91\x7c\xfb\xc0\xc0\xa2\xbf\x89\x8b\xd5\x2d\x36\x43\x8c\xdf\xd7\x28\xf9\x1b\xb9\x58\x5d\x87\x6c\x5c\xac\x71\x31\x74\x52\xbe\xb5\x6f\x5a\xa4\xcc\xf1\xf4\xc0\x00\x7c\x55\x37\xdd\x1a\x26\xb1\xdd\xdb\x3e\xf0\x74\x61\xb1\x2e\xc6\x35\xf9\x17\x9b\xf7\xc5\x9a\x99\x46\x89\x26\xa4\x8c\xf1\xb7\x01\x88\xb5\xfb\xd6\xe6\xb6\x04\x42\xf5\xe9\xc2\xd3\x03\x9f\xa2\xe7\x88\xec\x4d\x3b\xd3\xcc\xc4\x50\x07\xda\x8b\x9c\x17\xf6\xde\xe8\x02\x4c\xdd\x26\xad\xb9\xa6\xba\xec\xa6\x2d\x50\x26\x39\xd6\xa4\x2d\xcf\x9a\x7c\x8e\x6d\xc6\x65\x1f\x9c\x54\x32\x95\xb4\xe6\x6d\xf9\xea\xb0\xd6\xac\x37\x41\x0e\x6f\x17\xcd\xc9\xee\x9d\xf2\x46\x6a\x82\x76\x1f\x34\xc4\x38\x9f\xd7\xdf\x90\x57\xac\xf9\x51\x28\xc8\xf7\x44\xf8\x3a\xd6\x74\xbd\x09\x35\xf5\x77\x95\xdf\x35\x72\xb1\xa8\x35\xf3\xc5\x51\x26\x56\x9e\xf3\x5e\x07\x30\x0c\xcb\x38\x7d\xcd\x5c\x33\xeb\xc1\x36\xc5\x9a\x29\x9d\x68\xe6\xbf\x83\xd3\x83\x4d\xe7\x1b\x17\xe0\xae\xd5\x77\x31\x5f\x19\x06\x8b\xa3\xbf\x9d\x8e\x84\xf0\x3e\x26\x4e\xb1\x57\xc5\x29\xff\x33\x4f\x8f\x14\xe1\x87\x66\xd7\xa6\x09\xa1\x03\xa9\xec\xf1\x42\x10\x8e\x13\xad\xde\x97\xf0\xd0\xbe\x08\xd7\x47\xf8\x5a\x6e\xb0\xb6\x4b\x6a\x6b\x8d\xf3\x4d\x75\x91\x70\xc0\x4b\x98\x67\xe7\xaa\xe3\x6e\x5e\xe0\x04\xc7\x87\x88\x5b\xde\x5c\x04\xcb\x7d\xf9\x37\x45\xf1\xb2\x59\xda\x32\x7e\x5c\x1d\xa2\x6a\xdb\x12\xb3\xea\x1f\xc2\x24\x08\xca\x50\x4d\x05\x3c\x48\x6a\xc5\x76\x5f\xa2\xfa\x6e\x42\xc3\x03\x5e\x87\xe5\x77\x1c\x03\x25\x28\x04\x95\x6c\x26\x47\xad\x66\xd9\xcc\x9b\x4f\xcf\x11\x8e\xf9\x97\x7b\x07\x74\xbc\x3c\x37\x46\xdd\x5b\xd5\xb1\xda\x81\x76\x55\x6d\xaf\xf2\xc3\x0b\x56\xee\x82\x94\xdb\x10\xca\x74\xd1\x6c\x01\xe6\x07\xf6\x0e\xa8\x0c\x3f\x37\x46\x9d\xd7\xf4\xf6\x81\x81\xf6\x31\x74\x4d\xbd\x6a\x95\xae\x96\x4d\x29\x99\x9c\x3d\x2a\x56\xf4\xaa\xf3\x7b\x4e\xa9\x8b\x8b\xaa\x06\x73\x8b\xaa\x76\x7a\xcf\x29\x95\x6a\x6b\x75\xfd\xb4\xa6\xea\x15\xbd\x6a\xc5\xbe\x46\xda\xab\x35\xf5\xb4\x21\x21\x48\x2f\x98\x08\x9c\xd8\x02\x84\x4c\x28\x9c\x10\x14\x55\x5c\x22\xcd\x19\xb3\xaa\xbc\x48\x58\x1e\x7a\x53\xc4\xb8\x78\xe5\x8a\xae\xca\x57\x74\x22\xe1\x57\xf9\xac\x78\x50\x94\xd0\x1c\xaf\x79\x67\x26\xca\x92\xb6\xc8\x79\x13\x28\x33\x4a\x69\x03\x97\xa2\xea\x2f\xaf\x5a\x48\x53\xc7\xf3\xcb\x6a\x21\x6d\x78\x75\xbc\xbc\xb8\xa8\xaa\x6a\xba\x40\xf5\xf6\xe9\x82\xba\x68\xfa\xf8\x1a\x4c\x9c\xfa\x18\x88\xc8\x79\xa1\xa5\xa1\xa6\x6c\x73\x08\xb3\x91\x96\x0a\x26\xee\x86\x64\x8a\x84\x01\x90\x73\x14\xa4\xe1\xc8\x6b\x10\x7f\x4d\x72\x3b\x54\x6f\xed\x86\xf6\x60\x87\x57\xb5\xb3\x37\x8e\x3f\xfa\x1a\x1c\xbc\x6d\x7e\xe1\x8d\x85\x05\x86\x7f\xed\xd1\xfd\xa3\x0e\x87\xea\xed\x08\xb6\x6f\xa8\xf5\xaa\x36\x57\xfa\x35\x88\x1b\xa7\xe6\x6f\x3b\xb2\xb0\xf0\xc6\x02\xaa\xd6\xed\x06\xa8\xbd\x83\x0b\xba\xac\xf5\xe7\xc2\x3e\xec\x87\x2e\xdb\x2a\x09\xd5\x7e\xa5\x51\xbd\x13\xd5\x40\x59\xce\x51\x0c\xaf\x9d\xee\x9b\xff\xc4\x51\x9e\xf7\x76\xdc\x7b\xea\xb1\xbe\xd3\x37\x50\x46\xd5\xc2\xcf\x02\xc3\x23\x27\x8a\xa2\x24\xea\x22\xf8\xb9\x23\xd5\x52\x17\xf4\xb3\x96\x6c\x42\x66\xc7\x09\x89\x4c\xae\x80\xe9\x26\x21\x5c\x34\xb6\x88\x0f\xe9\xb6\xac\x3b\x63\x78\x80\xf7\xde\x86\x7f\x8a\x71\xf6\xc5\x95\x67\x71\x69\xd1\xce\xc5\xbe\x7b\xba\xb4\x23\x91\x74\x84\x33\x6d\xfc\x97\x2d\xd9\x62\xe5\xbf\x33\x4e\x96\xf9\xd9\x95\x70\x33\xcc\xaf\x3c\xab\xc6\xe0\x44\xa9\x28\xdd\x9b\x6e\x73\x44\x8a\x03\xdb\x9b\x8a\xde\x55\xbf\x23\x95\xee\xf7\xf2\x3d\xab\x8a\x3e\x07\x94\x20\x11\xc5\xe6\x4d\x77\x75\x72\xd0\xca\xbe\x10\xb4\x7c\xb5\x2f\x96\x79\x0a\x2d\xb3\x44\x55\x19\xb6\xec\x23\x54\x3e\xfe\xd4\x72\xa6\xe1\x65\x1a\xae\xdd\xe3\x96\x2e\xbd\x6c\xc3\x00\x01\x82\x14\xfd\xcc\xd3\x9b\x5a\x96\x05\xe3\xaa\xf2\x36\xc6\x1c\xaf\x69\x29\xa9\x94\x2f\xdb\x49\xac\xf9\x99\x72\xb8\x83\xcc\xcf\xbe\xaa\xaf\x82\xa0\xae\xeb\x70\xb9\xaa\x75\x54\x55\xde\xb4\x05\x54\xb7\xaf\x04\xe9\xed\xaf\xcb\x7a\xb9\xfd\x95\x67\xab\xe6\xca\x54\xf9\x13\x0b\x26\x40\xac\xf7\x65\x99\xa0\xac\xf3\x12\x38\x25\x48\xce\x99\x18\x54\x28\x78\x4d\x9d\x30\x0d\xa8\xdf\x14\xbd\x3d\x58\x85\xcb\xab\xf5\xc9\x1f\xc4\x5a\x2e\xab\xdf\x50\x75\xd5\xf2\x8f\xc7\x25\xfc\x35\x14\x40\x1b\x90\xf3\x42\x37\x67\xd1\x42\xea\x82\x43\xdd\x01\x70\x26\xc7\x56\x9c\x72\xac\x0b\x03\x39\x53\x8c\x63\x39\x42\xf4\x04\x2a\xb9\x75\x01\x29\x09\x11\x9f\x93\x6a\x50\xbb\x2e\xda\x18\xea\xa4\x43\x84\xb8\xf6\xd8\x23\xb7\xcb\xe9\x82\x31\x5b\x48\xcb\xb7\x3f\x12\xc6\xcb\xb6\x42\xda\xa6\xa7\x0b\x85\xb4\x8e\x85\x81\xbf\x82\x18\x07\x32\x91\xe9\x4c\x01\xf3\xe8\x33\xb5\xb7\x15\xd2\xe9\xc2\x6d\xb5\xcf\x1c\x95\x4d\xf7\x1d\x02\x97\x8a\x4d\xa4\x02\x37\x4b\x51\x7d\x59\xaf\xf6\xf3\xe7\x51\x2b\x72\x5e\x88\x0b\xb5\xd5\xfc\x66\xd9\xbf\xc1\x1a\xab\x68\xb9\x15\x47\xd7\xba\x10\x5f\x51\xd3\x85\x95\x67\x0b\x69\xf5\x0a\x17\x0b\x8f\x9a\x8e\xc3\x86\x97\x8b\xa9\x64\x34\x44\xb8\xbd\x4c\x99\xcf\x59\x98\xa7\xb6\x8d\x73\x8c\xbb\xfa\x4e\x02\x58\x1a\x6a\x2a\x60\x65\x33\x39\x08\x44\x2e\x3d\x73\xdd\xa6\xb6\x37\x3e\xf9\xcc\xa5\xc7\xf1\xf2\xa6\xeb\x9e\xb9\x14\xb9\xfb\xf1\x4b\xcf\x7c\xf2\x0d\x54\x2d\xaf\x47\x89\xbc\x6a\xfa\xc5\x89\x9c\x92\x8d\xb2\x59\x91\x62\xeb\xac\xc8\xa5\xf2\x9c\x92\x15\xe1\xa0\xa9\xc5\x53\x2d\x6d\x9e\x4a\x71\xe0\x62\x55\xe6\x1a\x7a\x15\x43\x8e\xf3\x01\xd3\x2f\xd2\xd4\x94\x2b\x72\x34\x6b\x63\x57\x13\xc1\xa2\x8d\xe1\xa9\xa3\x45\xb4\xd1\xf8\x9f\xbf\x6f\xc5\x6c\xe1\xd2\xdd\x3f\x66\xf8\x95\x37\xa9\xef\x85\xeb\x75\x67\xbb\x71\xc2\x8c\xc2\x13\x36\x5f\xbc\x21\x62\xda\x73\xfe\x13\x7e\x09\x2f\x23\x09\x39\xce\xa7\x4c\x99\xde\xe4\x98\xe2\x84\xe3\x89\xfa\x81\x60\x9c\x4d\x50\x84\x54\x17\xb0\xe0\x83\x68\x44\xde\x84\xa3\x2c\x25\xd2\x00\x7f\xf2\x74\x61\x60\xe0\x4a\xeb\xe1\x91\xd6\x89\x2d\x37\xb1\xac\xaf\x8e\xbd\xd2\x04\xb1\xd8\xbe\x66\xe3\x92\x0c\x4a\x66\x21\xe3\x3a\xb9\xe5\xde\xd1\x1f\x3f\x35\x0a\x11\xbc\x5c\x28\x3c\x3d\x70\xb9\x75\xe4\x70\xeb\xe4\x96\x83\x8c\x33\x12\x70\xfe\x6b\xf3\xee\xe6\x18\x34\x1b\xff\x98\x99\x57\x32\x19\xef\xef\x6c\xb9\x77\xf4\xa9\x1f\x8f\x42\x60\xad\x2c\xee\x5c\xc5\x49\x10\xa5\x8e\x7d\x62\x96\xde\x06\x53\x4d\xed\x72\x39\x86\xd6\xe8\x7f\xbc\x28\x8c\x9a\x51\x3b\xc1\xb1\xc9\x96\x7a\x2e\x58\x43\xfd\x41\xc0\x12\x86\x72\xd9\x4c\x2a\x67\xb3\x30\x12\x35\xfc\x8a\x24\xc7\x1e\x91\xf3\x24\x21\x64\x33\x39\x9c\x2c\x96\x4a\xc5\x06\xc1\x35\xd2\x65\xfc\x88\x3a\x28\xe1\xdf\x89\xa7\x75\x69\x66\xe5\xa5\x6d\x6f\x6c\x8b\xa7\x0d\x6f\x9a\xe1\x4f\x3d\x73\x2a\x94\x6a\xff\xc4\xfe\x92\x7a\xea\x99\x53\xa7\x9e\xf9\x56\x70\x53\xf2\x99\xa1\x91\x22\x13\xda\xbb\x97\x44\x9f\x49\x6e\xba\xda\xc7\xd0\xbf\x56\x6e\x66\xaa\xfa\x84\x8d\xa4\x95\x53\xae\xf1\x9f\x8f\x8f\x1e\x7b\xef\xed\x63\x0c\xff\xd8\xeb\x8f\x3d\xf6\xba\xf1\x79\x55\xb5\x9d\xd8\xad\xaa\xbb\x4f\x94\xe5\x23\x7a\x8f\xc2\x63\xdd\x69\x0a\x53\xd7\x66\x66\xbd\x9e\x41\x08\x96\x3d\x7c\x43\xf0\x7a\x28\x64\xc8\x21\x15\xa4\xdf\x2b\xeb\x9d\x2c\x75\x83\x4a\xd0\x19\xbd\x21\x11\x6b\xaf\x96\xbd\xec\xc8\x8f\x22\xf4\xbe\x94\xc7\xc1\x98\x76\x9c\x68\x10\x72\xf6\x32\x9b\x0c\x09\xf3\x3a\x18\x1c\x6c\xa8\x33\x7e\x30\x03\x6d\xf7\x3d\xf7\xda\xa3\x1f\x9f\x86\x80\x71\xf8\x1f\x2f\xe6\x8a\x0c\xcf\x18\x27\x20\x7d\xf8\xb6\xd0\xab\xf7\x3f\xfa\xda\x6e\x08\xfc\xed\xa5\x8b\xed\xf7\x6c\x3d\x5d\xb6\x21\xd2\x73\x42\x6d\x88\x18\xd6\xdd\xdd\xa1\x67\x3a\xc8\xa6\x82\xe5\x83\x4d\x08\xfc\xcd\x54\x4a\x82\x79\x8a\x4d\x28\xaf\xd0\x27\x19\x17\x0b\x69\xd5\xbc\xad\x63\xdd\xd1\xd1\x0b\x69\x53\x8e\x4c\x17\x74\x68\x5e\xe7\xab\xe3\xbe\x96\xaf\x4e\x62\xf5\xee\x65\xd5\xfd\xc6\x38\xd9\x4e\xd7\xc0\xdb\xa0\x98\xde\xb8\x54\xf6\x94\xf4\x8a\x8e\xd0\xb2\xf3\x9a\xc6\x0a\x5c\xe5\x07\xde\x8c\x9c\x17\xea\xac\xbd\xab\x04\x45\x26\xca\x5a\x1a\x92\x4c\xb2\x1b\xf2\xa6\xcc\xc1\x53\x29\x93\xf0\xd4\xf7\x15\xdb\x0e\x2e\xbc\xb1\x90\xe9\x4c\x14\x93\xc5\x91\xd7\x8c\xb7\x5e\x1b\x29\x12\xf6\x14\xd0\xce\x96\x1d\xf7\x1c\x59\x58\x38\x52\x38\x2c\xb6\xec\x6c\xe9\x9d\x1f\x7b\xf4\xb5\xd7\x1e\x1d\x9b\xef\x5d\x2f\x2f\x87\xe8\x7e\x88\x84\x5d\x26\xad\xa0\xf6\x18\xc1\xf4\xb5\xa7\xda\x42\xb2\x2f\x84\xa0\xa0\x13\x32\xf9\x0a\xe3\x73\xb9\x0e\xac\x3c\x7b\xc0\xa5\x9b\x8e\xd4\xba\xea\x32\x7e\xe2\xd2\x75\x17\x34\xb8\x74\x93\x8e\xdb\xaf\x21\x53\xf8\x07\xbd\xb5\x6b\xfc\x94\xaa\xee\x19\x46\xad\xc7\xd4\x1d\x1b\x5e\xaa\x9a\x55\x29\xfb\xb0\x99\x3e\xa6\x73\x21\xdd\x03\xef\x58\xf6\x42\xeb\x8e\x0f\x44\x42\x45\xc8\xd3\xcb\x29\x29\x07\x9b\x64\x1c\x98\x4d\xa6\xc4\xac\xd2\x02\xb9\x5a\xd8\xd2\x70\xab\x7f\xc0\x1f\x1e\xfe\xd3\x9f\xd6\x6c\x4f\x46\x26\xb9\x3d\x5c\x53\xdf\xc9\x65\x17\xe3\xdf\x8f\x97\x3f\x62\x7c\x25\xf1\x99\x44\xf2\x41\xb5\xe9\xc5\x03\xd7\x43\xdf\xb0\xf1\x2f\xc3\xa5\x3f\x53\xe3\x8e\x5e\x68\xac\xb2\x57\xf7\x23\xc7\xf9\x16\x6b\xaf\x51\x4e\x0e\x58\x07\xa7\x70\x72\xbe\x88\x2d\x3d\x5a\x17\xa4\x92\x22\x27\x66\xf2\x45\x6c\x72\xe7\xe1\x66\x88\x46\x38\x31\xa8\x64\x52\x5d\x58\xef\xdf\xdf\xef\x0a\x30\xcc\x4f\x23\x0e\x36\x16\xad\xe9\xe8\xef\xef\xa8\x89\xc6\x58\x47\xe4\xa7\x0c\x13\x70\x95\x5f\xd2\x04\x2e\x7d\xc0\xdb\xf5\x55\x71\x89\xe6\x94\xef\xb4\xc1\x2f\xf0\x32\x95\x9f\x9c\x17\xba\x24\xbf\xe5\x17\x6a\x0e\xd5\x67\xa3\x18\xaa\x08\xf9\xa2\x33\x9b\x49\xd1\x9b\x3c\x6c\x2a\x97\xcf\x11\xa1\xfe\x5f\x3f\x71\x5d\xa1\xa3\x3d\x1e\xf5\x34\x3b\x5d\x6a\x5b\xbb\x80\x1b\x6d\x9c\xcd\xcf\xb0\xb8\x29\xb9\x77\x57\xa2\x8e\x55\xdd\xbd\x43\x23\x64\x45\x0a\xb9\x8f\xe4\x3e\xda\xd5\x1c\xf6\x08\x91\xb6\xba\xfa\xfa\xdc\xc1\x9e\x18\x8e\xda\xc3\xce\x90\x2f\x12\xf5\xa5\xeb\xe3\x89\xba\x4e\xef\xae\xde\xa1\x95\xcd\xea\x5a\x5c\xda\x82\x14\xb4\x0d\x1d\x20\xb8\x74\xef\x0d\x83\x7d\x52\xab\xdb\xc2\xa5\x96\xa3\x2a\xdb\x05\x62\x9c\xed\x82\xb8\x83\x25\x23\xb3\x7c\x6e\x99\x55\x0e\xe3\x6a\x9e\xc3\xca\x49\x5c\x95\x63\xfa\xd0\x6e\xea\x0d\xee\xe8\x6a\x8c\xd5\x8c\x1e\x1a\xad\x89\x35\xf6\xb7\x65\xe7\xc6\x82\x8f\x34\x58\xec\x47\xac\x3d\x16\x7e\xa9\x2a\x0e\x9f\xb3\x9c\x87\x49\xa2\xec\xc7\xab\x8c\xd6\xb4\x67\xbc\xbd\x9b\x36\xf5\x7a\xe3\x03\x37\x50\x7f\x5e\x2f\x61\x4a\xa8\xba\x59\x27\x44\x9f\xa4\x4c\xb5\x0b\xf6\xc7\x6c\x6b\x52\x55\x67\x5f\x2c\xeb\xba\x89\x4c\x23\xb2\x2d\x60\xd2\xf3\x78\x37\xb0\x8e\x0e\xc8\x2a\xf9\x6e\x10\xb3\xa6\xfd\xb8\x00\x44\xbe\x91\x9f\xf8\x73\x47\x44\xd7\xeb\xbb\xf8\x67\x9e\x68\xe9\x56\xd5\x67\xbe\xc0\xa8\x6a\x17\xff\xc4\xc3\x2d\xdd\x32\x35\xdf\xbd\xf2\x3a\x43\x48\x7c\xf2\xe5\x97\x93\xea\xeb\xaf\x30\x6a\xf2\xdc\xb9\x24\x5a\x43\xef\xb9\x0a\xbd\x97\xa9\x21\x2f\x29\x52\x3c\x9e\x8f\x52\x51\x27\x07\xf3\x67\xef\xba\xcb\xb8\x78\xe7\xf8\xdd\xa7\x35\xed\x57\xa5\x91\x93\xcf\x7e\xf1\xdd\x77\x19\x9e\xe6\x1d\xd0\x7e\xa5\x95\x46\x4e\xbe\x0b\xdb\xdf\x5d\xdb\xa6\xab\x72\xa7\x59\x08\xa6\x20\x2a\x04\xa3\x40\xf8\xe4\x67\x8c\x59\xf2\xdf\x74\x9e\x32\x63\xcf\x98\xbc\x90\x45\x63\x2a\xf5\x1a\x41\xe4\x14\x0f\x28\x59\xf1\xca\x15\x5d\x27\x4f\x15\xd6\x5b\xbd\x93\xbd\xda\x0f\xa7\x64\x6b\x20\x2b\x72\x89\x55\x4c\x49\xe0\x43\x6b\xaf\xc5\x5b\x6e\x54\x6b\xe2\xad\x90\xcf\x43\xe9\x58\x50\xb6\x45\x6a\x39\x31\x83\x5b\x45\x4e\x46\xf4\xea\x1a\x27\x47\x38\xf1\x2c\xa4\xbf\xff\xbb\xfa\x53\xc6\x3f\x3c\xfd\x3a\x24\xf5\x3f\xbf\x77\x6c\x4e\x67\xf8\xdf\xfd\xbe\xf1\x83\xb3\x4f\x43\xf8\x29\xd5\x78\xf3\xf5\x7b\xff\x5c\x9d\x1b\xab\xde\xbb\x6e\xea\x15\x2a\x92\xbd\xcb\x37\x45\xc3\x7e\x0f\xe5\x03\x42\xb2\x8d\xb4\xdf\x85\x45\x4e\x6e\x86\xd5\x0e\x32\x49\xb1\xac\x82\x50\x5f\x86\xa1\x65\xf5\xa3\x4f\xfd\xb7\x7b\x1e\xfa\xc9\x83\xea\xa5\x67\xa8\x00\x9d\xb6\x54\xa4\xcb\xc6\xcb\x2f\xdf\xf3\xdf\x9e\xfa\xa8\xfa\xe0\x4f\x1e\x7a\xe6\x92\x29\x6c\x57\xdf\x77\x31\xfb\x16\x90\xe3\x7c\xd0\x84\xb9\xe5\x6a\x69\xfa\xc5\x07\x2b\x9a\x52\x93\x18\x50\x3f\x2b\x98\x27\xdc\x74\xac\x3d\x06\xf3\x44\x2c\x26\xdc\x69\x7b\xac\xc0\xf0\x85\xb4\x4e\xf5\xa4\x57\x8c\x8b\x94\xbb\x5e\x34\xaf\x39\xa6\x0b\xd5\x3a\xd9\x65\x54\x8b\x62\xc8\x79\xa1\x21\xe4\xa6\x78\xa3\xec\xbf\xb1\x7a\xa9\x40\xa0\xbb\xd6\xc1\x16\x41\xd9\xf1\x8e\x0e\xf3\xaa\xd4\x62\x14\x5b\x24\x09\x4b\xba\xae\x6f\xbd\x23\x10\x7c\xfb\x09\xbc\xac\x1a\x5e\xf5\xde\x16\x49\x6a\xc1\xc9\x16\x49\xd7\xd5\xfd\xc3\x8b\xdf\xae\xb2\xa5\x78\xcb\xfa\x77\x17\xa6\x6d\x65\x8a\x58\x31\x2d\x66\xea\xe3\xdf\x4b\x76\xde\xa9\x2f\xe0\x65\xe3\xc7\xc6\xd7\xf5\x3f\x5e\x98\x7b\xee\x3f\x6c\x5b\xe7\xa3\xdf\x84\x9c\x17\x22\x9c\x87\xca\x22\xf9\x68\xc4\x16\x35\x55\x6b\x54\xe3\xb6\x11\x72\xad\xa1\xa0\x3d\x12\x8d\x44\x23\x70\x6f\x28\x94\x00\xdf\xf6\xc7\x36\x0e\x8e\xcc\x8e\x0c\x6e\x7c\x6c\x3b\xf8\xfa\x8c\x37\xbd\x47\xfb\xe4\x0e\x26\xea\x72\xf9\x4f\x34\xb7\xbc\xfd\xd7\xbb\xbb\xb3\xd9\xee\xdd\xdf\x78\xab\xa5\xf9\xc4\xca\xcd\x78\xd7\x3d\x17\x1e\xdb\xde\xda\xf2\xa1\xef\xc5\x97\x1f\x42\x8f\x2a\xe6\x4e\xeb\x56\x3c\x75\x14\xb2\xec\x7e\x50\x65\xf7\xb3\xe6\x0e\x0a\xa7\x44\x59\xea\x7f\x99\xca\x2b\x20\x99\x95\x75\xea\x96\xa3\xab\x2a\xd5\x6b\xac\xc3\x9f\x0e\xe4\x46\x3e\x54\x4b\xc6\x12\xf4\xd7\x78\x5c\xac\xb9\x07\x15\xaa\x74\x21\x6b\x55\x96\xe7\x74\x95\x1e\x0f\x5d\x37\x75\x6f\x84\x28\xaf\x3c\xab\xeb\x65\xaa\xf9\xe1\xdb\xac\x72\x81\xb2\xda\x2c\xeb\xf1\xc8\xb1\x5d\xdb\x26\x5b\x59\xdf\xb5\x16\xea\xd0\x60\x20\xd1\xba\x7a\x7b\x8e\xb5\xee\xce\xad\xb9\xaa\x12\xa5\xfe\xc2\x82\x75\x6f\x6d\x35\xdc\x81\xcf\x90\x6d\xb4\x32\xd7\x22\x49\x7d\xaa\x4a\xd5\x0f\x95\x5f\xbc\x2c\xb5\xac\xbc\x59\xde\x66\xab\x57\xd9\xac\xfb\x6c\x6b\x71\x96\x79\xbf\x28\x24\xb0\xa1\x14\x1b\x4a\x09\x30\xaf\x82\x74\x45\xbd\x72\x45\xbd\x52\x61\xbb\xa8\x41\x18\x55\xd9\xa2\x4d\x1f\xe1\x16\x94\x42\xdd\x28\x8f\x36\xa1\x6d\x28\x3a\x18\xde\x3c\xb8\xa1\x2f\xd3\x23\xb5\xb5\xf2\x4d\x75\xa6\xcf\xb0\x6b\x8d\xf1\x6a\xcd\xed\x2e\xdb\x6f\xc8\x6f\x5d\xf7\x1e\x3e\x4e\xd9\xea\x1f\x9a\x57\xbd\x82\x34\x30\x7e\x42\xd5\x41\xa6\x32\xc6\x72\xd2\x5e\xf9\x46\x75\x89\x1f\x5a\xbc\x38\x65\xbe\xb7\x55\xdd\x05\xdb\x46\x35\x46\x7c\x55\xce\x0f\xab\xdf\x9a\xcc\xfa\xaa\x4d\x9a\xac\x5b\x3d\x72\xbf\x10\xa5\xcb\xc4\x5c\x63\x99\x4c\x59\x20\x22\xe7\xe2\xf8\x6e\x5f\x38\xec\x5b\x79\xc8\x17\x0e\x8f\xc2\xe5\x1f\x0e\xc4\x9a\x36\x34\x31\x7c\xd8\xb7\xd2\x4a\xf2\xf1\x7f\xf5\x85\x2d\x89\x40\x96\xe5\xea\xf3\x6b\x33\x7d\x7d\xca\xbe\x87\x2e\x9c\x2b\x5b\x91\x24\xb8\x6c\x5c\x34\xbc\xe5\x3b\x81\xf4\x73\x1a\x97\x75\x13\x1b\x32\x15\xba\x60\xa3\x58\xde\xfd\x82\xdf\xe3\xb4\x33\xa6\x5f\x1b\xb0\x90\x50\x80\x4d\xe5\xcb\xda\x34\x9d\xde\x04\x87\xbd\x4f\x81\xa4\xef\xbc\xcb\x68\x37\x61\x55\x32\x2e\x9a\x2f\xf4\x9d\xf7\xe8\x55\xbe\xed\x6b\xd7\xba\x66\xd0\x5d\x5e\xd7\x6b\xf8\xfd\x97\xd7\x2b\x77\x6d\xdf\x7f\xe3\x93\x74\xa9\x7e\xb9\x77\x60\x60\xef\xc0\x7a\x27\x6f\xd3\x97\x6c\x80\xbc\xab\xc2\xb7\x2e\xea\x87\xe4\xb5\xfc\xe5\x04\x56\x08\x0a\xd9\x7c\x50\x80\x28\xa4\xf2\x51\xdc\xad\x1a\x4f\xc0\x65\x95\x30\xbd\xaa\x31\x6b\xba\x52\x18\xde\xcf\xab\xc6\x13\xe5\x8c\x6a\xfa\xe7\x41\x41\xc4\x21\xf7\x0b\xa1\xda\xc0\x35\xe4\x38\xd3\x65\x9b\x13\xe8\xf5\x07\xd7\x7b\xbf\x74\x2d\x8e\xa8\xea\xc8\x73\x3b\xc9\xfe\x2f\x5f\x47\xdd\x49\xb3\x64\x75\xad\x9e\xbb\x1e\x39\xce\xd7\x94\xfd\x06\x32\xc9\x0e\x08\x56\x3b\x07\x88\x71\x07\x27\x32\xbc\x5c\x48\x2f\xc6\x38\xd9\x98\x95\xb9\xd8\x62\xba\x20\x33\xbc\x9e\x2e\xac\xbc\x69\xda\xfe\x71\xb2\x90\x2e\xd3\x19\xc6\xd2\xa1\x11\x3a\xc3\x55\xfb\x7c\xc9\xd1\x5a\xd3\x5d\xca\xc1\x85\xcd\xab\xbc\x5d\x38\x95\xa4\x3e\x60\x07\x05\xdf\xe1\x18\xb7\x18\x6e\xee\x76\xd4\x26\x5b\x3d\xb8\xd8\xe7\x6c\x92\xf0\xb2\x31\x0b\xf3\x2d\xc6\x0f\x9b\xb8\x58\x4b\xa8\xe5\x89\x70\xc2\x1b\x0c\x33\x1f\x3f\xe2\x6c\x2a\xcb\x4e\x78\x19\x35\x51\x1b\x63\x4b\x2c\x8c\xa9\xee\x8a\xc8\x4a\x54\x76\x2a\x80\xa5\x0f\x75\x88\x42\x90\x70\xef\x54\xf4\x98\x0f\xb4\x84\x42\xb5\x8e\xee\xe6\xf0\x22\x17\xcb\x34\xd4\xfb\x04\xe3\xac\xd4\xe4\xec\x2b\x1a\xc7\x55\x15\xff\x71\x0b\xc3\x38\x12\xe1\x27\x5a\x42\x2d\x31\xce\xfb\xd4\xf6\xeb\x5b\x60\xbe\xc9\x79\xe4\xe3\xc6\xbf\x19\xb3\x65\x7d\x19\xe9\xb3\x05\xb5\x23\xe7\x05\x91\x8f\x9a\x3a\x71\x4e\xa4\x37\xe4\x4c\xe6\x2b\x97\xcd\xa4\xa8\xa7\xa2\x29\xae\x65\x45\x3b\x32\x95\x20\x45\xc8\xeb\x20\xa9\x72\x24\x9e\xee\xa6\x2a\xe4\xc0\x81\x7a\x22\xba\x3d\x77\x9f\x0a\x97\x8d\xbf\x0b\xfb\x93\xcd\x7e\xd7\xd7\x0e\xe0\x65\xd5\xe1\x74\x1e\x0c\x2c\xbc\xb1\xb0\x70\xa4\x3b\x9d\xd8\xdc\x4f\x44\xb8\xfb\xbf\x59\xcb\x04\x8c\xa7\xc3\xfe\xe6\xa4\xdf\xb5\xb8\x6d\xcd\x1d\x50\x7a\xaf\x91\x5d\xd5\xa7\x67\x05\xf3\x73\x34\x54\x8b\x4e\xfd\x8b\x67\xa9\x07\xbf\xa9\x3f\xa7\xca\xf3\x75\x77\x0f\xfd\xf4\xae\x96\x18\x70\x5a\x77\xae\x23\xab\xfa\xb2\x4c\x8e\x51\x82\x26\xf3\x51\x15\x9a\xdf\x1d\x50\xd3\x05\x35\x5d\x58\xd9\x5c\x48\xab\x85\xcf\x53\x73\x3d\x79\x42\x86\x1c\x22\xdc\x48\x21\x9d\x2e\x10\xf9\xdb\x74\xd6\x80\x83\x95\xb0\x25\x14\x5a\x73\xef\x91\x43\xce\x0b\xe1\x5a\xaf\xc9\x8f\xac\xf6\x61\x3a\x8a\x30\x02\x27\xd0\xef\x5f\xa8\x73\x63\xf2\xe1\xc7\x54\x42\x77\x09\x0b\x4a\x3f\x2a\x31\x6f\xcc\x8e\xcd\x9d\xda\x63\x5c\x5c\xf7\x1d\x2a\x0f\x8a\x52\xca\x54\x33\xe8\x4e\xb6\xc6\xea\x82\x5e\xf3\x1e\x8b\x75\x99\x9c\x1c\x16\xa1\x2a\x6e\xaf\x8a\x83\xc0\x09\xae\xe7\x5d\xae\xe7\x5d\x78\x92\x06\xc6\x0f\xcd\xe0\xa2\x49\xf4\x5d\x46\xb3\xcb\x05\x3f\x72\x95\x43\x9d\x48\xc2\x44\x0e\xb6\xec\xc8\x6c\x95\xad\xcb\x4f\x69\xa4\x88\x3a\x90\x42\x68\x64\xb7\x94\x6a\x6d\x69\x6a\xb0\x70\x0f\x4b\xc7\x43\x85\x35\xc1\x0a\xed\xe5\xd0\x24\xc8\x74\x44\x89\x75\xa1\xfb\x82\x4b\xc7\x53\x17\x5c\xba\xf1\x16\xf9\xa1\xa3\xd2\x5d\xcf\xbb\x40\xd2\x5d\x17\xdc\xe6\x2f\x5e\x76\xbd\xf2\x8a\x8b\x3c\xaa\xeb\xc0\x01\x97\xae\xaa\x24\x75\xc0\xe5\x3a\xf0\x8a\xcb\xb5\xce\x97\xc0\x4f\xbf\x3a\x51\x33\xe8\x16\x9a\xb8\x80\xab\xea\x1b\x5b\x1f\x00\xab\xea\x38\xe9\xdb\x84\x15\x64\xab\x03\x22\xd1\xaf\xc2\xc9\x04\x4f\x39\x8d\xd6\x7c\x97\x89\x2b\xe3\x1d\xf3\x8a\x51\x5e\x09\x8a\xa9\x2a\x56\x54\xa6\xee\x00\x2a\xf5\x35\xf2\x72\xb1\x18\x2e\xc5\xb8\x45\xb2\x81\xcd\x8f\x3c\xcc\x73\xb1\x35\xfe\x88\x4d\xa8\x15\xb9\x5f\x68\x8d\xd5\xba\xac\xbb\x8b\x41\x31\x25\x06\x15\x21\x2b\x47\x9b\x21\xec\x60\x45\x87\x98\x95\xf3\x45\xc8\x24\x53\x49\x96\x66\xf9\x01\x76\x0e\x0d\x3d\x48\xf8\x82\x44\x72\xea\x77\x7f\x77\x8a\x89\x27\x1b\xea\xae\xdb\xb7\xef\xba\xba\x86\x7a\xa1\xbd\x83\xaf\xb7\xbc\xb8\x55\x77\xfd\x9f\xbc\x71\xb6\xde\xa5\x3a\x82\xb7\x7f\xea\xf6\xa0\x43\x65\x9c\xf9\xcd\x79\x27\x53\x65\x03\x32\xbf\x3d\xe4\x77\x56\x74\x38\xa4\x6f\xfb\xfa\x6e\xd6\xb6\x5a\x6e\xa5\xda\xdf\x95\xad\xdc\xcb\xb6\xda\xb0\xea\x5a\x75\xd6\xf8\x98\xf9\x2d\x3d\x0e\x57\xa6\xe7\xa0\xc8\x51\x88\xd2\xef\x82\x6c\x84\x3c\xe4\x18\x4e\xc9\x92\x07\xb2\x63\x4e\x1b\x24\x19\xd7\xca\x7e\x17\x03\x49\x1b\x98\xdf\x28\x58\xd9\x1f\xda\xed\x72\xed\x0e\xe1\x2f\xb9\x08\x77\x44\x50\x43\xa5\xfd\x5f\x50\xbe\xaa\x16\x35\x10\x7e\x21\x54\xe3\x2e\xeb\xf6\xa8\xe4\x9e\xca\xd5\x96\x63\x91\x32\x67\x73\xd7\x1b\x5f\xca\x3e\xfc\xe2\xf9\x47\xb2\x5f\x2a\x5d\x9a\xce\x8e\x1c\xd8\xb7\x3d\xf7\x9c\x45\xea\xef\x1c\x3b\x70\x60\xec\x4e\xe3\x62\x71\x5f\x77\xf7\xbe\x9f\x95\xe5\x1f\x5c\xb1\x73\xd5\x23\x01\x39\x2f\x34\x37\xf8\xcd\x7b\xb9\x51\x96\x53\xf2\xd6\xe7\x36\x80\xf0\xfd\x99\x3c\x10\x64\x8a\x2c\xa2\x8c\x53\xbc\x7f\x52\x9f\x64\x1e\x3b\xdc\xde\x63\xbc\xdd\x54\x6a\x8a\xc5\x40\x0c\x3d\x66\xbc\x64\xb1\x49\xcc\xa4\x3e\xe9\xe7\x8f\x2c\x84\x8c\x1f\x36\x35\xc5\xf6\xc7\xa0\xa5\xa7\xe3\xc8\xc2\xca\xdc\xaa\xbf\xdc\xfb\xcf\xd1\x3b\xf0\xd1\xca\xfe\x8b\x44\x8b\x38\x1f\x65\x9b\x71\x94\xed\xc2\x29\x36\x95\xcb\x27\x53\x0f\xc3\x7b\x8d\x87\xc6\x5a\x1f\x59\x69\x3a\xb4\x2f\xf9\x48\xeb\x9e\x99\xc6\x95\x87\x95\x6f\xfd\x1b\xe0\x65\xf8\xb7\xa6\x99\x3d\xc9\x87\x57\x1a\x67\xf6\xb4\x3e\x92\xdc\x77\xa8\x69\xe5\x91\x9e\x6f\xbd\x07\x6b\xe4\x00\xd2\xb6\x8f\xd2\x54\x25\xa4\x84\x14\x46\xb1\x38\x6d\x91\x20\xe8\xac\x90\x55\xd4\x9f\x26\x7f\xaa\xbe\xf3\x8e\xe9\x3a\xa8\x9a\xc1\x3b\x65\x8f\x42\xc2\xe5\xea\xd6\x5d\x0a\xa6\xca\xc6\x99\x42\x0a\x72\xbf\xd0\xd5\xd6\xdc\x50\x63\xf9\xdc\x7e\x80\x9d\xb3\xd5\xf4\xf1\xca\xe7\x36\x41\x2e\x9b\x49\x28\x9c\x79\xb5\x4e\xcc\x2a\xd4\xee\xe9\xb4\x1f\xae\xd8\x3d\x6d\xbb\x6e\x7a\xf4\x35\xf8\x26\xa1\x37\x37\x6f\xf3\x05\x99\x6c\xf9\x8a\x2f\xc3\xbf\xf6\x68\x69\xb7\xcd\x5e\xb6\x81\x1e\xb6\x3b\xa5\xd7\x20\x7e\xeb\xc2\x1b\x0b\x1b\xa2\x4e\x66\x78\xfc\x48\x57\xd5\x9d\x88\xaa\x31\x76\x20\xf7\x0b\xed\xbf\xed\x18\x09\xda\xfb\x10\x63\x9b\x35\x1d\x2f\x7f\xd3\xc8\x72\xe6\xb7\x0e\xa0\xfe\x89\x0e\xcf\xa7\xea\x3f\xea\xdf\xf0\x3f\x91\x87\x7e\x89\x73\xed\xf7\x3d\xd1\xfb\x9f\xa5\xa3\x46\xc8\x59\xf9\x7a\x26\x10\x8c\xf2\xfe\x67\xd1\x08\xf3\x00\x05\x7f\xfc\xaa\xef\x68\xbe\x03\x3c\xfd\xe6\x24\x02\x37\x29\xfb\xeb\x1f\x5c\xfa\x70\xef\x48\xbc\x3a\x4d\xce\xfa\x87\x69\xb7\x1c\x56\x97\xff\x75\x7d\x56\xb7\xff\x41\x7d\xac\x1f\xcb\xff\xae\xe7\x5a\x7d\xae\x9f\xe7\x87\x7e\x1e\xb8\x7a\xbe\xff\x2b\xc6\xf8\x61\xd7\xe5\x43\xc1\xf4\x01\x2b\xef\x81\xd5\xf7\xbf\x0e\x06\xbf\xb6\xfd\xe5\x0f\xbf\xef\xd6\xcc\xe7\xf2\x87\x9f\xc3\xff\xee\xbd\xb0\x06\xd6\x0f\xac\x8d\x5b\x7f\x11\xfa\xaf\x0f\xdd\x84\xee\x46\x8f\xa3\xaf\xa1\x9f\x80\x0f\x6e\x80\x29\x78\x08\xfe\x0c\xbe\x07\xff\x82\xbd\x38\x8e\xfb\xf0\x4e\xac\xe2\x7b\xf0\xa7\xf1\xf3\x0c\x62\xe2\xcc\x5e\x66\x8e\xd1\x99\x9f\xd9\xbc\xb6\xfd\xb6\x4f\xd8\xbe\x64\xfb\x1f\xb6\xf7\xec\x92\xbd\x68\x3f\x68\x7f\xd2\xfe\x7d\x07\xef\x28\x39\x7e\xc4\x46\xd8\x12\xfb\x19\xf6\xc7\xec\x65\xa7\xc3\x59\xe7\x6c\x77\xde\xed\x5c\x74\x3e\xef\xfc\x8e\xf3\x47\xce\x5f\xb9\x7c\x2e\xd9\x75\xd0\xf5\xc7\xae\xef\xb8\x03\xee\xad\xee\xdb\xdc\x4f\xb9\xbf\xe9\xf1\x7a\xda\x3d\x23\x9e\x23\x9e\x07\x3d\x57\xbc\xb5\xde\x1b\xbc\x47\xbd\x4f\x79\x2f\xd6\x38\x6a\x26\x6a\xee\xab\x79\xb2\xe6\xeb\x35\xbf\xf4\xb5\xfa\xe6\x7c\x0b\xbe\xe7\x7c\xdf\xf7\x19\xfe\x9c\x7f\xa7\xff\x90\xff\x01\xff\x53\xfe\xbf\x09\x38\x02\x7d\x81\xfd\x81\xb9\xc0\x67\x02\xe7\x02\xdf\x0f\xb6\x05\xef\x08\x3e\x14\xfc\x4e\xf0\x52\xad\xa3\xb6\xad\x76\xb6\x76\xa1\xf6\xf9\x50\x31\x34\x11\xfa\x5e\xb8\x21\xbc\x35\x7c\x5f\x58\xe7\x10\xd7\xcc\xf5\x71\x37\x71\xf7\x71\x7a\xc4\x16\x49\x46\x6e\x88\x1c\x8f\x3c\x1e\xd1\x23\xef\x45\xe3\xd1\xc7\xeb\x22\x75\x8f\xd7\xbd\x51\xdf\x56\xbf\xa1\x7e\xa2\xfe\x93\xf5\x5f\xaa\xff\x5e\xbd\xd1\xb0\xa1\xe1\x48\xc3\x57\x1a\xbe\xd3\xf0\x56\xc3\xe5\x46\x6f\x63\x6b\xe3\x86\xc6\xbd\x8d\x6a\xe3\x27\x1a\x9f\x6c\xfc\x79\x13\x6a\xea\x6b\x2a\x35\xdd\xd7\x74\xae\xc9\x88\x35\xc4\xd4\xd8\xa7\x62\x2f\xc5\xfe\xa1\x39\xde\x3c\xd8\x7c\x73\xf3\x67\x9a\x5f\x6d\xfe\x59\x4b\xbc\x65\xae\xe5\x6b\x7c\x91\xbf\x9b\x3f\xcb\xbf\xcc\x7f\x9f\xff\xb9\xd0\x2c\xdc\x2c\x2c\x08\x5f\x17\x7e\x14\x6f\x88\x0f\xc5\x27\xe2\x67\xe2\x5f\x89\xbf\x27\xd6\x89\xf3\xe2\xab\xe2\x8f\x5a\x7d\xad\xdb\x5b\x67\x5b\x9f\x6c\xfd\x66\xeb\x95\x44\x26\x71\x4f\xe2\xc9\xc4\x9b\x49\x5b\x72\x6b\xf2\xf1\xe4\xdf\x25\x8d\x54\x31\x35\x95\xba\x3b\xf5\x6c\xea\xbf\xb6\xd5\xb5\x6d\x6d\x9b\x6d\x7b\x35\xed\xb5\xe8\xe6\x8f\xd0\x11\xeb\x13\xca\x26\x26\x3e\x81\x10\xca\x79\xee\xb0\xd6\xd9\x87\xfe\xac\x82\x87\x87\xd0\x83\x56\x1c\x90\x1d\xfd\x17\x2b\x8e\x91\x03\xfd\xbf\x56\x9c\x41\xad\x30\x60\xc5\x6d\xc8\x05\x27\xac\xb8\x1d\xf9\xe0\xb3\x56\xdc\x81\x3c\xf0\x2a\x62\x10\xd8\x08\x5b\xfa\x00\xad\x45\xe2\x80\xdc\xe8\x3f\x5b\x71\x8c\xbc\xe8\xff\xb6\xe2\x0c\x1a\x41\x3f\xb7\xe2\x36\x54\x0b\x1f\xb5\xe2\x76\xd4\x04\xf7\x5a\x71\x07\x8a\xc0\x97\xd0\x30\x3a\x8e\x66\xd0\x24\x3a\x49\x3f\xb1\xcd\xa3\x29\xf4\x31\xc4\xa3\x51\xd4\x85\xb6\xa3\x2e\xc4\xa3\x5d\xe8\x28\x3a\x86\x8e\xa3\x5b\x11\x8f\xee\xa2\x1f\xe3\x3e\x82\x78\xb4\x95\x7e\xb2\xfb\x24\x0d\x8f\xa3\xc3\x68\x06\xf1\x48\x46\x5d\xa8\x87\x7e\xad\xe9\x08\x3a\x89\x4e\xa2\x59\x34\x80\xba\x51\x37\x3a\x64\x95\x3d\x54\x29\xdb\x85\x4e\xa0\x43\xa8\x0b\x1d\x45\x33\xe8\x24\x4a\x23\x34\x7c\x7c\x66\xf2\xe4\x8c\xca\x4f\x7d\x8c\x1f\xed\xda\xde\xc5\xef\x3a\x7a\xec\xf8\xad\xfc\x5d\xb7\x9e\x3c\xc2\x6f\x3d\x76\xf4\xe4\xd6\x63\xc7\x0f\xcf\xf0\x72\x57\x0f\xdf\x76\xe4\xe4\xc9\xd9\x81\xee\xee\x43\xc7\x8e\x9e\x3c\x44\x72\xbb\x4e\x1c\xea\x3a\x3a\x73\x32\x8d\xaa\xbf\x35\x7e\xfd\xf4\xb1\xa3\x27\xd0\xda\xef\x8d\xdf\x3a\x7d\xec\xe8\xad\xd3\xe8\xd7\x8e\x7c\x00\xf1\xd7\xf8\xe8\xb8\x99\xdf\x8b\x3a\x51\x1e\x75\x22\x19\xf5\xa0\x5e\x94\x41\x68\xed\xc0\x06\xf8\x7d\x47\x4f\xde\x7a\xf2\x8e\x19\xb5\x97\x1f\xe0\x7b\x3b\xf3\x9d\x72\x4f\x6f\x06\x5d\xeb\x1b\xe6\x95\x82\xe8\xd7\x7f\x01\x7d\xff\xcc\xf1\x13\xb7\x1e\x3b\xca\xf7\x76\xf5\x76\xf5\xf2\x08\x1d\x43\xb3\x68\x06\x1d\x45\x9d\x6b\x27\x76\x6c\x76\xe6\x68\x67\x79\x76\x16\x5f\xf0\xfe\xfd\x48\x46\xd7\xfa\xfb\x11\xdd\x87\x18\x30\x30\x60\x03\x3b\x38\x80\x05\x27\xb8\xc0\x0d\x1e\xb4\x19\x6d\x41\x5b\xd1\x36\x34\x82\xae\x07\x2f\xd4\x80\x0f\xfc\x10\x80\x20\xd4\x42\x08\xc2\xc0\x41\x04\xa2\x50\x07\xf5\xd0\x00\x8d\xd0\x04\x31\x68\x86\x16\xe0\x41\x80\x38\x88\xd0\x0a\x09\x48\x42\x0a\xda\x20\x0d\xed\xd0\x01\x12\x74\x42\x17\x74\x43\x0f\xf4\x82\x0c\x0a\x64\x20\x0b\x39\xc8\x43\x1f\xf4\xc3\x00\x14\x60\x03\x6c\x84\x22\x6c\x82\x41\xb8\x0e\x86\x60\x18\x36\xc3\x16\xd8\x0a\xdb\x60\x04\xae\x87\xed\xb0\x03\x6e\x80\x1b\x61\x27\xec\x82\x51\xd8\x0d\x63\xb0\x07\xf6\xc2\x3e\xd8\x0f\x1f\x81\x12\x8c\xc3\x4d\x70\x00\x6e\x86\x5b\xe0\x20\x7c\x14\x26\x60\x12\xa6\x60\x1a\x54\x98\x81\x43\x70\x18\x8e\xc0\xad\x70\x1b\xdc\x0e\x77\xc0\x9d\x70\x14\x8e\xc1\x2c\xfc\x0e\x1c\x87\x13\x70\x12\x4e\xc1\x1c\xdc\x05\x77\xc3\xc7\xe0\x1e\xf8\x38\x9c\x86\x7b\xe1\x13\xf0\xbb\x70\x1f\xfc\x1e\x7c\x12\xee\x87\x07\xe0\xf7\xe1\x41\xf8\x03\x78\x08\xfe\x10\xce\xc0\x7f\x80\x4f\xc1\xc3\xf0\x69\x78\x04\xe6\xe1\x51\x58\x80\xc7\xe0\x33\xf0\x1f\x61\x11\xfe\x08\x1e\x87\x27\xe0\x8f\xe1\xb3\xf0\x24\xfc\x27\xf8\x1c\x7c\x1e\x9e\x82\x3f\x81\xb3\xf0\x05\x78\x1a\xbe\x08\xcf\xc0\xff\x01\xcf\xc2\x9f\xa2\xa7\xe0\x4b\xf0\x65\x78\x0e\xfe\x4f\xf8\x33\xf8\x73\xd0\x60\x09\xce\xc1\x79\xf8\x0a\x5c\x80\xe7\xe1\x05\x78\x11\xbe\x0a\x2f\xc1\x5f\xc0\x32\xfc\x25\xbc\x0c\xaf\xc0\xd7\xe0\xaf\xe0\x55\xf8\x6b\xf8\x3a\x7c\x03\xbe\x09\xdf\x02\x1d\xfe\x33\xfc\x0d\x7c\x1b\xbe\x03\xaf\xc1\xeb\xf0\x5d\x78\x03\xfe\x16\xbe\x07\xff\x17\x5c\x84\xff\x02\xdf\x87\x1f\xc0\xdf\xc1\xdf\xa3\xab\x81\x8a\x7b\x7b\x70\x6f\x2f\xee\x95\x71\xaf\x82\x7b\x33\xb8\x37\x8b\x7b\x73\xb8\x37\x8f\x7b\xfb\x70\x6f\x3f\xee\x9d\xc4\xbd\x53\xb8\x77\x1a\xf7\xaa\xb8\x77\x06\xf7\x1e\xc2\x72\x0f\x96\x7b\xb1\x2c\x63\x59\xc1\x72\x06\xcb\x59\x2c\xe7\xb0\x9c\xc7\x72\x1f\x96\xfb\xb1\x3c\x89\xe5\x29\x2c\x4f\x63\x59\xc5\xf2\x0c\x96\x0f\x61\xa5\x07\x2b\xbd\x58\x91\xb1\xa2\x60\x25\x83\x95\x2c\x56\x72\x58\xc9\x63\xa5\x0f\x2b\xfd\x58\x99\xc4\xca\x14\x56\xa6\xb1\xa2\x62\x65\x06\x2b\x87\x70\xa6\x07\x67\x7a\x71\x46\xc6\x19\x05\x67\x32\x38\x93\xc5\x99\x1c\xce\xe4\x71\xa6\x0f\x67\xfa\x71\x66\x12\x67\xa6\x70\x66\x1a\x67\x54\x9c\x99\xc1\x99\x43\x38\xdb\x83\xb3\xbd\x38\x2b\xe3\xac\x82\xb3\x19\x9c\xcd\xe2\x6c\x0e\x67\xf3\x38\xdb\x87\xb3\xfd\x38\x3b\x89\xb3\x53\x38\x3b\x8d\xb3\x2a\xce\xce\xe0\xec\x21\x9c\xeb\xc1\xb9\x5e\x9c\x93\x71\x4e\xc1\xb9\x0c\xce\x65\x71\x2e\x87\x73\x79\x9c\xeb\xc3\xb9\x7e\x9c\x9b\xc4\xb9\x29\x9c\x9b\xc6\x39\x15\xe7\x66\x70\xee\x10\xce\xf7\xe0\x7c\x2f\xce\xcb\x38\xaf\xe0\x7c\x06\xe7\xb3\x38\x9f\xc3\xf9\x3c\xce\xf7\xe1\x7c\x3f\xce\x4f\xe2\xfc\x14\xce\x4f\xe3\xbc\x8a\xf3\x33\x38\x7f\x08\xf7\xf5\xe0\xbe\x5e\xdc\x27\xe3\x3e\x05\xf7\x65\x70\x5f\x16\xf7\xe5\x70\x5f\x1e\xf7\xf5\xe1\xbe\x7e\xdc\x37\x89\xfb\xa6\x70\xdf\x34\xee\x53\x71\xdf\x0c\xee\x3b\x84\xfb\x7b\x70\x7f\x2f\xee\x97\x71\xbf\x82\xfb\x33\xb8\x3f\x8b\xfb\x73\xb8\x3f\x8f\xfb\xfb\x70\x7f\x3f\xee\x9f\xc4\xfd\x53\xb8\x7f\x1a\xf7\xab\xb8\x7f\x06\xf7\x1f\xc2\x93\x3d\x78\xb2\x17\x4f\xca\x78\x52\xc1\x93\x19\x3c\x99\xc5\x93\x39\x3c\x99\xc7\x93\x7d\x78\xb2\x1f\x4f\x4e\xe2\xc9\x29\x3c\x39\x8d\x27\x55\x3c\x79\x08\x4f\xf5\xe0\xa9\x5e\x3c\x25\xe3\x29\x05\x4f\x65\xf0\x54\x16\x4f\xe5\xf0\x54\x1e\x4f\xf5\xe1\xa9\x7e\x3c\x35\x89\xa7\xa6\xf0\xd4\x34\x9e\x52\xf1\xd4\x0c\x9e\x3a\x84\xa7\x7b\xf0\x74\x2f\x9e\x96\xf1\xb4\x82\xa7\x33\x78\x3a\x8b\xa7\x73\x78\x3a\x8f\xa7\xfb\xf0\x74\x3f\x9e\x9e\xc4\xd3\x53\x78\x7a\x1a\x4f\xab\x78\x7a\x06\x4f\x1f\xc2\x6a\x0f\x56\x7b\xb1\x2a\x63\x55\xc1\x6a\x06\xab\x59\xac\xe6\xb0\x9a\xc7\x6a\x1f\x56\xfb\xb1\x3a\x89\xd5\x29\xac\x4e\x63\x55\xc5\xea\x0c\xc1\x1f\xf0\xfe\xfb\xc8\x5f\x8d\x56\x34\x24\xf1\x1a\xda\x57\xda\x32\xce\xf3\x3b\x5e\x44\xbe\xdd\x3b\x34\xc7\x9e\x9b\x4a\x5a\xa6\x51\x6b\x1b\x9f\x38\xc4\x9f\xd9\x57\xd2\x70\x62\xf2\xab\x4e\xe4\x44\xd3\xd3\xe2\x54\xa3\x20\x68\x68\x5c\x43\xc3\xe2\xe6\x73\x08\xd0\xf0\xc4\x50\xa7\x06\x92\xc6\x4f\x1c\xea\xd4\xb0\xc4\xab\xbc\xf6\xb5\x51\xcd\x96\xbc\xe9\x5c\x1b\xb8\x87\xb7\x4c\x6f\x19\x3b\x50\x12\x44\xa1\xf1\x4c\x89\xd7\x46\x47\x4b\x82\x36\x38\xde\xc8\x6b\x7d\x24\xd6\x37\x3e\xce\x2f\x99\x85\x26\x55\xad\x6d\xb4\x24\x58\x29\x5e\xeb\x21\xef\x7b\x48\xc9\xaf\x8d\x96\xf8\x43\xfc\x99\x33\x93\xbc\xe6\x1e\x2d\x4d\x34\xf2\x1a\x4f\xde\xb9\x49\x2c\x47\x62\xb9\x89\xc6\x89\xf1\xf1\xf1\x46\x0d\x3a\xc6\xc7\x45\x0d\x8d\x96\x66\xc6\xc7\x3b\x35\x46\xe2\xb7\xf0\x9a\x2d\x31\xa9\xf2\x9a\x7d\x78\xb4\xa4\xd9\xc5\x21\xcd\x21\x0e\x35\x0a\xc2\xb8\x06\x13\x9d\x9a\x4d\x12\x05\x51\xe0\xd5\x25\xfb\xd4\x10\x4f\xde\x98\x9d\x6b\xee\x61\x0d\x4d\x6c\xd1\x98\x76\x81\xd7\x1c\xc3\xfc\x19\xfe\x8c\x06\x1d\x4b\x3d\xf6\xc4\x99\xdd\xa5\x89\xd1\xc6\xc9\xb1\xf1\x92\x38\x2e\xf0\xda\xe0\x9e\x92\x06\x1d\x8d\x64\x42\x56\xaf\x9d\x9a\x5d\xd2\xd8\xe1\x8e\x73\x08\x9b\x60\x71\x48\x1a\x2b\x0e\x89\xbc\x86\xc4\xa1\x49\x0d\x4f\x1d\xd2\x60\x5a\x83\x09\xcd\xde\xde\xa9\xb1\x12\x4f\x06\xe8\x19\x9e\x7e\xd1\x86\xa6\x78\xd2\x82\x36\x38\x31\x4e\x8a\x4c\x6c\xa6\x03\x74\x4a\xe7\x58\x0f\x1a\xde\x32\xd4\x2e\x54\x00\xed\x92\xd6\x02\xde\x6d\xb6\x02\x1d\xa2\x86\x86\x35\x5b\x62\x82\xdf\x72\x46\x9c\x24\x8b\x40\xa1\x84\x1a\x09\x24\x35\xbe\x51\x1b\xac\xc0\x46\x63\x12\xe2\xe4\x66\xb3\x0b\xcf\x07\x54\xd7\x5a\x47\x4b\xa4\xf2\xe0\xb5\x2a\x79\x25\x3a\xa1\x73\x1e\x37\xb3\xa5\x24\x34\x8a\xc2\x78\xbb\xd0\xa9\xd5\x48\x4b\x18\x6f\xd1\xd4\xc9\xcd\x9d\x9a\x4f\xd2\x60\x82\xe7\x35\xef\xf0\x76\x52\x9d\xd7\xbc\xe2\xd0\xb8\x56\x43\x52\x63\x25\x5e\xab\x11\x87\xc6\x3b\x35\xbf\xc4\x6b\x01\x0a\x12\xfe\x45\x1b\x9a\x3e\x23\x4e\x6a\xbe\xe1\x09\xfe\xcc\x04\xaf\xf9\xc4\x21\xb1\x53\x0b\x48\x3b\xf6\x96\x96\x6c\xea\xe6\xf1\x56\xad\x66\x46\xbc\xbb\x53\x0b\x4a\x3b\x76\x97\x76\xec\x31\x33\x1b\x85\xf1\x56\x2d\x44\xf3\x6b\xa5\x25\xe4\x1f\xde\x57\x5a\xf2\xfb\x87\x35\x98\x1c\xd2\xfc\x1d\x64\x83\x6a\x38\x31\xb4\xe4\x25\x3f\x35\x38\x31\xa4\x41\x44\xe4\x35\x26\x31\x5a\x5a\x22\xc0\xd3\x6c\x89\xa1\x33\x67\x78\xda\x6d\xbb\x20\x6a\x30\x59\x8e\x37\x9a\xef\x49\x15\x9c\xa0\x39\xe3\x9a\x77\x78\x9b\x56\x33\xbc\x6d\x42\xc3\x6b\x97\xea\x03\x16\x70\x09\xa1\x90\xb8\x59\x83\x61\x0d\x15\xcf\x01\x00\x5d\xab\x90\x84\x96\x10\xde\xb2\xb7\xa4\xf9\xc5\x21\x7e\x8b\xe6\x11\x87\x34\xb7\xa8\xa1\x89\x21\x7e\x42\x83\xc9\x0b\x81\x00\x20\x1f\x1a\x1a\x3a\x33\xb1\x54\xeb\xe8\xd0\x8e\x77\x34\xc6\xc7\x3b\xb5\xb0\xb4\x84\x42\x1d\x9d\x1a\x27\x2d\x01\x09\x23\xd2\x12\x26\x61\x54\x5a\x62\x48\x58\x27\x2d\xd9\x48\x58\x2f\x2d\xd9\x49\xd8\x20\x2d\x39\x48\xd8\x28\x2d\xb1\x24\x6c\x92\x96\x9c\x24\x8c\x49\x4b\x2e\x12\x36\x4b\x48\xab\xe9\xf8\x77\x0c\xa4\x45\x5a\x42\xcd\x1d\x9d\x1a\x2f\x2d\x01\x09\x05\x69\x09\x93\x30\x2e\x2d\x31\x24\x14\xa5\x25\x1b\x09\x5b\xa5\x25\x3b\x09\x13\xd2\x92\x83\x84\x49\x69\x89\x25\x61\x4a\x5a\x72\x92\xb0\x4d\x5a\x72\x91\x30\x2d\xf1\x1b\xe8\x7e\x6a\x97\xf8\x09\x2d\x30\xc1\x0f\x8b\x1a\x4c\x0c\x53\x98\xc3\x84\x96\x26\x9b\xaa\x43\xd2\xda\x3b\xb4\xf6\xf6\x4e\x4d\x92\x78\x7e\x1b\xff\x01\xe0\x16\x27\xfb\x44\x82\xa7\x7e\x6d\x89\x46\x61\xbc\x53\xeb\xac\xac\x01\x44\x34\xa9\x5d\x03\xae\x87\x4e\xae\xab\x1a\x2a\x6b\x5f\x75\x4b\x7c\x96\x8e\xb3\x47\x42\x1a\x6c\xb9\xba\x71\x0d\x3a\xae\xd9\x29\xc9\x47\x91\xaf\x50\x24\xbb\xb9\x28\xf6\x2d\x75\x03\xd7\xde\xa9\xf5\x4a\xfc\x06\x7e\xdb\x07\x8c\x53\x43\xc3\x93\x7d\x9d\x9a\x2c\x75\x45\x37\x74\x6a\xca\x6f\x2a\xaa\xc1\xf0\x74\x5f\xa7\x96\x91\x96\x30\x8a\x24\xf8\x2e\x7e\x1b\x39\x99\x1a\x4e\x5c\x7f\xe6\xcc\x36\x71\x9b\x38\xc9\x97\xa6\x1a\x09\xbe\x13\x87\xce\x29\x00\x5c\xb8\xbd\x53\xcb\x4a\x1a\x8a\x68\xb6\x84\x66\x4b\xd0\x22\x9a\x6b\xb8\x63\xe6\x4c\x97\xc8\xf3\x1b\xce\xf4\x75\x6a\xb9\xd5\xd7\x7c\x97\xd9\x86\x66\x13\x87\x48\x29\x5e\x9b\x20\x87\x79\x70\x77\xe9\x3c\xe6\x19\xbe\xf1\x3c\x4e\x32\x0d\xe3\x43\x04\xc1\x39\x87\xf9\x33\x22\x2d\x2d\x6e\x9d\xd0\x6c\xc3\xeb\xcf\xc9\x04\x41\x32\x26\x16\xc7\xc3\x13\xaa\xa8\x31\xc3\x93\xea\x68\x49\xc3\xc3\x93\x8d\x1a\x33\x3c\x41\x10\xcc\xfa\x3a\x93\x22\xcf\x6b\xb6\xa4\xb8\x75\xb2\xaf\x51\xd4\x9c\xc3\x5b\x35\x9c\xd0\x9c\xc3\xb4\x97\x09\xfe\x5a\x9d\x88\x26\x2a\xb3\x0d\x4f\x10\xd8\xdb\x13\x93\x9a\xfd\xaa\x56\x35\x5b\x92\xcc\x28\x41\x07\x91\x98\x50\x47\x4d\x14\xb6\xda\xd7\x78\xa7\x96\x27\x30\xe0\x79\x5e\xb3\x27\x2d\x18\x88\x1b\xfa\x3a\xb5\x3e\x9a\xad\x39\xc5\x21\x9e\xe7\xb7\x8a\xdb\x48\x67\x64\xb5\xfa\x29\xc8\xc8\x04\x2c\x88\xa2\xbd\xa5\x2e\x7e\x83\x28\x34\x92\x11\x5b\x99\x3c\x19\x4b\x19\xe4\x8e\x84\x66\x4f\x5c\x5f\x4d\x56\xcd\x85\xba\xd6\xce\xb5\x56\x46\x24\xdb\x77\xc0\x1a\xc1\x70\x79\x69\x26\x08\xdd\x5d\x3f\xc5\xf2\x52\x16\x24\x91\xef\x22\x50\xdb\x3a\x56\xe2\x37\x8c\x77\x2d\x75\x40\xb8\xa3\x53\xdb\x50\xc9\x1e\xad\xce\xde\xb8\xb6\xf4\x35\xcb\x14\x25\x2d\xdb\x71\xcd\x46\x37\x49\x5a\xae\xe3\x0c\xcf\x6f\x20\x9b\xe5\x4c\xdf\x35\xca\x68\xb6\xe1\x2e\xad\xa3\xa3\x53\x1b\xac\xec\xb0\x32\x74\xc9\xe6\x12\xf9\x0d\x7c\x97\xd8\x67\x35\x77\x9d\xb4\xe4\xb4\x25\x86\xfe\x1d\x5b\x71\xdb\xff\xaa\xdd\x47\x86\x4f\xf0\xca\x06\xb1\xaf\x51\xa8\x5a\x6f\x61\xdc\x1a\xe3\x10\x01\x46\x79\xfe\xc3\x64\xfe\x82\x68\x01\xc0\x9a\x47\x65\xca\x9b\x25\x0d\x71\xe6\xe1\x3c\x87\xc8\x39\x0c\x75\x69\xbd\xed\x9d\xda\x96\x0f\xc8\xdf\x2a\x2d\x21\x08\x87\x34\xb9\xbd\x53\xdb\x26\x69\x99\xf6\x4e\x6d\x84\x40\x6d\x8b\xc8\x77\xf1\x5b\xcf\x88\x93\x65\x38\x5d\x2f\x91\xed\xa8\x8d\x74\x74\x6a\xdb\xa5\x73\x08\x5d\xd7\xd1\xa9\xed\x90\xce\x21\x20\x91\x1b\xa4\x73\x40\x73\x6e\x94\xce\x01\xcd\xd9\x49\xca\x0c\x75\x74\x6a\xbb\x48\x19\x12\x19\x25\x65\x48\x64\x37\x29\x43\x22\x63\xa4\x4c\xb1\xa3\x53\xdb\x43\xca\x90\xc8\x5e\x52\x86\x44\xf6\x91\x32\x24\xb2\x9f\x94\x19\xec\xe8\xd4\x3e\x42\xca\x90\x48\x89\x94\x21\x91\x71\x52\x86\x44\x6e\x22\x65\x86\x3b\x3a\xb5\x03\xa4\x0c\x89\xdc\x4c\xca\x90\xc8\x2d\xa4\x0c\x89\x1c\x24\x65\x36\x75\x74\x6a\x1f\x25\x65\x48\x64\x82\x94\x21\x91\x49\x52\x86\x44\xa6\x24\x2d\x5f\x01\xf3\x34\x49\x68\x85\x8e\x4e\x4d\xa5\xb1\x0d\x1d\x9d\xda\x0c\xdd\x4f\xf9\x0e\x6d\x63\x47\xa7\x76\x48\xd2\xfa\x2a\xa5\x0f\x93\x04\x2d\x7d\x84\xc6\x48\xe9\x5b\x69\x8c\x14\xbd\x4d\xd2\xfa\x2b\x45\x6f\x27\x09\x5a\xf4\x0e\x1a\x23\x45\xef\xa4\x31\x52\xf4\xa8\xa4\x0d\x54\x8a\x1e\x23\x09\x5a\x74\x96\xc6\x48\xd1\xdf\xa1\x31\x52\xf4\xb8\x74\xde\x65\xc3\x65\xce\x68\xa8\x43\x73\xce\x68\x4c\xeb\xe8\xdd\x84\x9e\x74\xa2\x1d\x2f\x22\x7d\xac\xb4\x04\xf0\xe9\x71\x0d\xcc\xb3\x3f\xbb\x84\xec\x43\x4b\x08\xb5\x9b\xa1\x73\x4d\xf8\x55\x84\x90\xab\x3a\xc6\xa0\xcd\x4b\xad\xf0\xd0\xee\x92\x36\xf8\x50\x69\x89\x51\x37\x2f\x25\x49\xea\x25\xe7\x7d\x08\x6c\x83\x0f\x4d\xef\x2d\x91\x22\xe3\xe3\xe3\xe3\x4b\xc8\xdb\xfe\x22\xbc\xff\xfb\x9a\xed\xe1\x25\x8c\x36\xa3\xff\x2f\x00\x00\xff\xff\x4c\xbd\xec\x9b\x24\x6e\x00\x00") func staticFontsOpenIconicEotBytes() ([]byte, error) { @@ -129,7 +116,7 @@ func staticFontsOpenIconicEot() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/fonts/open-iconic.eot", size: 28196, mode: os.FileMode(493), modTime: time.Unix(1528532866, 0)} + info := bindataFileInfo{name: "static/fonts/open-iconic.eot", size: 28196, mode: os.FileMode(493), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -149,7 +136,7 @@ func staticFontsOpenIconicOtf() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/fonts/open-iconic.otf", size: 20996, mode: os.FileMode(493), modTime: time.Unix(1528532866, 0)} + info := bindataFileInfo{name: "static/fonts/open-iconic.otf", size: 20996, mode: os.FileMode(493), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -169,7 +156,7 @@ func staticFontsOpenIconicSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/fonts/open-iconic.svg", size: 54789, mode: os.FileMode(493), modTime: time.Unix(1528532866, 0)} + info := bindataFileInfo{name: "static/fonts/open-iconic.svg", size: 54789, mode: os.FileMode(493), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -189,7 +176,7 @@ func staticFontsOpenIconicTtf() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/fonts/open-iconic.ttf", size: 28028, mode: os.FileMode(493), modTime: time.Unix(1528532866, 0)} + info := bindataFileInfo{name: "static/fonts/open-iconic.ttf", size: 28028, mode: os.FileMode(493), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -209,7 +196,7 @@ func staticFontsOpenIconicWoff() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/fonts/open-iconic.woff", size: 14984, mode: os.FileMode(493), modTime: time.Unix(1528532866, 0)} + info := bindataFileInfo{name: "static/fonts/open-iconic.woff", size: 14984, mode: os.FileMode(493), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -229,7 +216,7 @@ func staticImagesGopher_fullPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/images/gopher_full.png", size: 69770, mode: os.FileMode(420), modTime: time.Unix(1528532867, 0)} + info := bindataFileInfo{name: "static/images/gopher_full.png", size: 69770, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -249,7 +236,7 @@ func staticImagesGopher_headPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/images/gopher_head.png", size: 81950, mode: os.FileMode(420), modTime: time.Unix(1528532867, 0)} + info := bindataFileInfo{name: "static/images/gopher_head.png", size: 81950, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -269,12 +256,12 @@ func staticImagesSpinnerGif() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/images/spinner.gif", size: 54072, mode: os.FileMode(420), modTime: time.Unix(1520794104, 0)} + info := bindataFileInfo{name: "static/images/spinner.gif", size: 54072, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _staticIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x5a\x6d\x6f\xdc\xb8\x11\xfe\x9e\x5f\x31\xc7\x62\x0f\x09\x70\x5a\x39\x35\x0e\x28\x1c\x69\xd1\x34\xce\x5d\x0c\x5c\x92\x43\xe2\x16\xe8\xa7\x05\x25\xce\x4a\x8c\x29\x52\x25\x29\xaf\xdd\xa2\xff\xfd\x40\x52\xd2\xea\x6d\x7d\x59\x27\x01\x02\x04\x6b\x91\x9c\x37\x3e\x1c\x0e\x67\xc8\x24\x3f\x30\x95\xdb\xfb\x1a\xa1\xb4\x95\xd8\x3c\x49\xdc\x1f\x10\x54\x16\x29\x41\x49\x36\x4f\x00\x92\x12\x29\x73\x1f\x00\x49\x85\x96\x42\x5e\x52\x6d\xd0\xa6\xa4\xb1\xbb\xe8\x6f\x64\x38\x24\x69\x85\x29\xb9\xe5\xb8\xaf\x95\xb6\x04\x72\x25\x2d\x4a\x9b\x92\x3d\x67\xb6\x4c\x19\xde\xf2\x1c\x23\xdf\xf8\x09\xb8\xe4\x96\x53\x11\x99\x9c\x0a\x4c\x9f\xff\x04\xa6\xd4\x5c\xde\x44\x56\x45\x3b\x6e\x53\xa9\x16\x44\x33\x34\xb9\xe6\xb5\xe5\x4a\x0e\xa4\xff\xca\xad\x56\xd9\x05\xfc\xde\x58\xcb\x65\x01\xb6\x44\x78\x5f\xa3\x84\x8f\xaa\xd1\x39\x02\x97\xf0\xfe\xe3\xd5\xbb\xeb\x05\x81\xb4\xb1\xa5\xd2\x03\x59\x6f\x79\x5e\x52\x14\xf0\x06\xa5\xe6\x37\x06\x25\x3c\xfd\x7b\xc5\xf3\xb2\x6b\x3e\x23\x9b\x27\x41\x8a\xe5\x56\xe0\x26\xe8\x4e\xe2\xd0\x6a\x87\x04\x97\x37\x50\x6a\xdc\xa5\x24\x36\xf6\x5e\xa0\x29\x11\xad\x89\x33\xa5\xac\xb1\x9a\xd6\xeb\xdc\x18\x02\x1a\x45\x4a\x0e\xe3\x9d\x79\xc7\xb8\x55\x8d\x92\xe7\x4a\xf2\xfc\x51\xec\x25\x2f\x4a\xc1\x8b\xd2\x3e\x8a\x9b\xd6\xb5\xe0\x39\x75\xc8\x1f\xe7\x4f\xe2\xe0\x2c\xee\x33\x53\xec\xbe\xc3\x43\xd2\x5b\xc8\x05\x35\x26\x25\x92\xde\x66\x54\x43\xf8\x13\xe1\x5d\x4d\x25\x8b\x2a\xd6\x75\x78\x03\x21\x2b\xc2\x47\x6b\x14\x40\xc2\x78\x2f\xc1\x2d\x15\xe5\x12\x75\x3f\x0a\x90\xd0\xb1\xfc\x28\xd3\x54\x32\xd2\x4d\x64\x48\xc9\xab\x02\x8c\xce\x53\x12\xf3\x8a\x16\x68\xe2\x42\xd5\x25\xea\xad\xb3\x7c\x5d\xcb\x82\x40\x70\x56\x72\x7e\x46\xa0\x44\x67\x46\x4a\xfe\x7a\x46\x3a\x05\x2c\xe2\x52\x70\x89\x51\x26\x54\x7e\x43\x80\x0a\x9b\x92\x81\x82\xce\x21\xe8\x40\x67\xd6\x58\xab\xe4\xc4\x44\xab\x8a\x42\xa0\x26\xe0\xf6\x5f\x4a\x02\x0d\x01\x46\x2d\x6d\xc7\xdc\x5c\x85\xa0\xb5\x41\x02\x54\x73\xda\xc2\x85\x2c\x25\x3b\x2a\xfa\x5e\x41\x33\xb7\x16\xd7\x9e\xc7\x01\xc9\x0b\xbf\x4e\x03\xa3\x00\x12\x53\xd3\x23\x16\x44\xce\xa9\xc8\x26\x89\x1d\xc9\xc0\xea\x38\x98\xd4\xaf\x41\xcc\xf8\x6d\xeb\x25\xb1\xa4\xb7\xdd\xe2\x56\x94\x4b\xd0\xca\x99\xeb\x3e\xc9\xf1\x75\x4a\x32\x0d\xf1\x68\x49\x39\x73\x3e\x44\xad\xd9\x2e\xae\xea\x60\xd5\x6b\xad\x0a\x8d\xce\xf1\xbc\xcf\xa5\x24\x2c\xcd\x05\x9c\x9f\xd5\x77\x2f\xc6\x53\x5d\x60\x8b\x9c\xd3\x0d\x1b\x91\xb1\x9a\xd7\xc8\xc6\x9d\x54\xf2\x8a\x5a\x64\xa4\x9d\x50\x37\x98\x51\x4d\xbc\xb1\x5d\xc7\xd6\xf7\xb4\xa6\x78\x87\xb9\x80\xe7\x67\x67\xab\x17\xed\x9a\xdc\x52\xd1\xa0\x54\xfb\x94\x3c\x3f\x3b\x1b\xf6\x55\x5c\xa6\x64\xdc\x43\xef\x02\xd5\xe6\x2a\x44\x44\xfe\x5f\x2e\x8b\xf5\x7a\x3d\x00\x7c\x82\xff\x0c\xcc\xf1\xa4\xb5\xda\x1f\x05\x24\x57\x22\x32\xd5\x68\x78\x42\x40\x35\x03\x8b\x77\x36\xca\x51\x5a\x6c\xe7\xed\x7a\xb7\x3b\x2e\x19\x97\x85\x99\x70\xcf\xf9\x23\xb7\xf9\x67\x54\xee\x2c\x39\x1f\x91\xf9\xa0\xb9\xa0\x60\xeb\x81\x21\x9b\xb3\x24\x2e\xcf\x17\xc4\xd4\x63\x29\x78\x67\x97\x84\xb8\xc3\x82\x6c\x7e\x69\x9b\x49\x5c\xcf\xcc\x1e\x23\xba\xd8\x35\xef\xf8\x6a\x60\x0a\xfc\x96\x48\x0a\xfc\x52\x18\x9d\x84\x0e\x43\x81\xdf\x1d\x80\xb9\xaa\x2a\x6e\xbf\x1d\x84\xad\xfc\x2f\x02\xb1\x93\x11\x60\x7c\x15\x5a\xdf\x1b\x90\x1a\x6b\x65\xb8\x55\x9a\x7f\x43\x87\x1c\x2a\xf9\x22\x48\x47\x82\x02\xae\x1f\x06\x5d\xdf\x1b\xb8\x96\xea\x02\xbf\xa1\x97\xb6\xf2\xbf\x08\xd2\x4e\x46\x40\xf3\x3a\xb4\xbe\x37\x20\x59\xa3\xe7\x59\xcd\xd7\x44\xb2\x53\xd0\x43\x79\x76\xe1\xff\x3d\x06\xd1\x5e\x56\x80\xf4\xb2\x6d\x7e\x1d\x4c\x47\xcd\xb6\x31\xce\xb0\xba\x96\xc1\xdc\xa9\x0d\x99\x0b\x2d\x70\xe9\x04\x4f\xc6\xb3\xeb\x8e\xcb\xa1\x7a\x2e\xeb\xc6\x76\xd3\xdd\x29\x5d\x45\x2e\x5b\xd3\x4a\xc0\xb0\x11\x99\x0a\x76\x42\x51\x1b\x69\x9f\xbb\xb7\x79\x6d\x40\xa6\x16\x34\xc7\x52\x09\x86\x3a\x25\x1f\x91\xea\xbc\x5c\xaf\xd7\x01\xb1\xfe\xc0\x36\xbe\x7f\x68\x9b\x87\xfe\xd0\xb4\x34\x13\xd8\x19\x12\x1a\xfe\xd7\xa9\x0e\x1f\xa5\xba\x45\xdd\x75\x86\x0c\x2f\x28\xf1\x5d\xcb\x19\x4c\x62\x0f\x25\xee\xa1\x4f\xcf\x56\xca\x96\x60\x72\x55\x87\xb4\x9c\x0c\x7d\x9a\xe6\xc1\x33\x5f\xe6\x61\x95\x6d\x79\x02\x73\x4d\x6d\x49\x36\xbf\x53\x5b\x9e\xc8\x18\x0e\x97\xee\x58\x39\x91\xb9\x0f\xa3\xf7\x83\xf8\x79\x3f\x17\x92\xc4\x63\x24\x1c\xc5\x04\xad\xc4\x86\x5a\x6f\x44\x34\xee\x4a\x62\x8f\xff\xc1\x69\x5b\xcf\xec\xca\x09\x57\x38\x6c\x92\x1f\xa2\x08\xe2\x75\x5f\x09\x40\x14\x75\x35\xc6\x4e\x29\x8b\xfa\xc1\x6a\x70\x18\x36\xc2\x77\xd5\xb8\x4c\x7e\x54\x24\x86\x7a\xb0\xb4\xb6\x36\x17\x71\x5c\x70\x5b\x36\xd9\x3a\x57\x55\x3c\x2c\xf1\x5d\xbf\x56\x19\x81\x10\x17\x53\xb2\xcd\x04\x95\x37\x64\x73\x28\xed\x80\x1b\xa0\xae\x74\xf8\x84\xb9\x85\xec\x7e\x2c\xfb\x22\x1e\xc9\x73\x0a\xe6\xc2\x66\x17\x0d\x5e\xee\x8f\x15\x67\x4c\xd9\x17\xa7\x1a\x1b\x73\x63\x1a\x34\xb1\xc4\xfd\x5c\x95\x5b\x5f\x6d\x81\x4a\xf0\x54\x83\xda\x74\x54\xd3\x75\x20\x87\x66\xb8\x68\x19\x6c\xe2\xd8\x62\x55\x0b\x6a\xdb\x98\xd9\xb5\xba\x3d\x75\xa8\xf2\x2c\x5b\xda\x1b\x87\x65\x58\x01\xdf\xc1\xd3\xb0\x57\x20\x4d\x81\xbc\x55\x8c\xef\xee\xc9\x33\xf8\x1f\xac\x8e\xd6\xac\x19\x65\x05\x82\xff\x8d\x6a\xcd\x2b\xea\x3c\xf7\xed\xfb\xcb\xab\x5f\xfe\x3d\xab\x5c\x57\xf0\x7f\x40\x61\x70\xaa\xe8\x4a\x1a\xd4\xf6\x04\x45\xa6\xc9\x73\x57\x74\x6e\x5e\x7d\x78\xfd\xf2\xfa\xf5\x67\x2b\xba\x44\x81\x16\x4f\x50\xc4\xa8\x2c\x5c\xed\x7b\xf9\xfa\xb7\xd7\x47\xf4\xac\x0e\x8b\x66\xd9\x11\xb0\x43\x2c\x49\x72\xc5\x70\xc1\xef\xff\x42\x36\xc9\x2a\x05\x5b\x72\xb3\x76\x91\x9b\x5a\x8b\xcc\xe5\xf6\x2e\xf8\x3c\x7d\x06\xab\xcd\xc8\x35\xbc\x94\x07\x94\x75\xf1\x27\xa8\xeb\xb5\x24\xab\x08\x42\x48\xfa\xa7\x16\xb0\xda\xb4\x57\x45\x52\xa9\x1a\xdd\x3e\x95\x4a\xe3\x0e\xb5\xbf\xf9\x98\x38\x6a\x6f\x5d\xa5\x18\x8a\xb5\x29\x95\xb6\x41\xd4\x1b\x6a\x0e\x16\x1e\x4c\x2b\x8f\x98\x36\x8c\x6e\x23\xc3\x0e\xa1\xee\x11\xc6\x0d\xd9\xdf\xef\x1d\xf9\x6a\x13\x8f\xbb\xdf\xd1\x0a\x7b\x2b\x3b\xf3\x92\x38\x6c\xa6\xc7\x6e\xad\x6d\xa5\x18\x15\x8b\x97\x61\x7e\x24\x72\x11\x79\x7c\x73\x52\xfe\x3c\xa6\x08\xc9\x8e\x9f\xc3\xe5\xe1\x0e\xd5\x5b\x5a\xfe\x3c\xbf\xa9\x1a\x5f\x49\x75\xc0\x0a\x65\xb0\xbd\xa0\x62\xdc\x54\xbc\x17\x3f\xbe\x88\x7a\xe5\xe9\xe6\x6e\xef\x69\x4a\xce\x18\xca\x94\x58\xed\x72\xac\x1f\x2d\xaf\xd0\xbc\x38\xe1\xea\x69\x69\xfa\x93\x84\xaf\x0d\x30\xde\x91\xb8\xb9\x46\x63\x3f\xa0\x83\x93\x3d\x7d\x36\xdf\x90\x03\x61\x54\xa0\x8b\x92\xee\x37\xda\x53\x2d\x5d\x50\x6b\xef\x81\x7c\x27\xd9\x24\xc6\x6a\x25\x8b\xcd\x3b\x65\x79\x8e\x17\x49\xdc\xb6\xe1\xba\xe4\x06\x5c\xc5\x0c\x42\xa9\x1b\x03\x56\x41\x86\x60\xd1\xf8\xfb\x68\x1d\xd4\xcf\x2e\x74\x46\xbb\x7a\x6c\x4b\x66\x65\x54\x68\xd5\xd4\xd0\x7f\x4d\xf3\xab\xd1\x34\x16\xd7\x6d\x90\x5c\x6d\x6f\x39\xee\xb7\x9a\xee\xc9\x40\x83\x97\x6d\x30\x57\x92\xf9\x68\xfa\x81\xee\xa7\xc8\x9f\x20\xbc\xc4\x3b\xd6\x54\xf5\x43\x0a\xde\xe0\x1d\x38\x9a\xb9\x96\x29\x34\xa3\x4c\xaf\x55\x13\x55\x68\x69\xe4\x47\x26\xf9\x9b\x9e\x26\x6f\xa5\xcf\xa7\x2e\x16\xd2\x19\xcb\xba\x78\xd5\xae\xdd\xf2\xb6\xee\x97\x36\x5e\xa6\xeb\xf7\x79\x4f\xb6\x8a\xa0\x0b\xa5\x7e\x60\x16\x3d\x61\x29\x9b\x5a\x32\xfd\xa5\x7f\x93\x38\x66\x7c\x1f\x5d\x03\x99\xd7\xf5\x08\x25\x6f\xd1\x18\x5a\xe0\xb2\x96\x43\xae\x2f\x6d\xc4\x2d\x15\x3c\x1f\x04\x67\xab\x1b\x99\x3b\x87\x0e\x76\xb4\x92\xda\xe8\xfc\x08\x53\xae\x2e\x8f\xcc\x75\x9a\xcd\x06\x48\x57\x11\x5c\xb1\x03\xc4\x53\xa2\xd6\x59\x87\xee\xc9\xd9\x36\x17\xbc\xce\x14\xd5\x6c\xe6\x9e\xaa\xb1\xfe\x3a\xbf\x77\xd3\xe0\xb4\x55\x1b\xe8\x7a\x46\x5f\xe2\x85\x43\xc4\xab\x77\xd1\x60\x70\x98\x2b\x0e\x8a\x1f\xa8\xfb\xeb\xf4\xa5\x0d\x35\x3e\xc2\xe7\x38\x4d\xb2\x65\x17\xce\x8f\xde\xf5\xce\x8a\x65\x1f\x12\xfd\xed\xdd\xd6\xd4\x5c\x4a\xd4\x8b\x77\xeb\xed\x4b\x48\x2b\xa5\xa5\x24\xe3\x97\x91\xb6\x77\x5d\xf0\x5d\xfb\xce\xf1\x9b\xa2\x0e\xd1\x10\xea\xda\x37\x33\xd3\x17\x72\x73\xd5\x64\x68\xb6\x2b\x9a\x37\xc7\x24\x8c\x4a\xe3\x69\x34\xe8\x9e\x0a\x06\x0a\x3a\xd6\x63\x93\xab\x35\x1e\x63\x71\x6b\x53\x6b\xfc\x33\xf2\x2e\x9e\x4d\xa9\x97\xca\xef\xf9\xba\x84\x93\x29\xa4\xd4\xc7\x1f\x62\x0e\x75\x0a\x0c\xf6\x5a\xf8\xde\xfb\x07\x8e\xee\x21\x6c\xc1\xd9\xfc\x48\xd6\x88\xac\x77\x36\xb8\xe6\xf5\x05\xfc\x43\xab\xbd\x41\xe8\x6a\x5d\x57\x9e\x34\xa6\x7b\x17\x5d\x90\x43\xb5\x56\xfb\x48\xe0\xce\x1e\x04\x51\xc9\x8e\x93\xb6\xe7\x4f\x4f\xeb\x3a\xe1\x06\xef\xcd\x7a\x7a\x90\x0f\x92\xcf\x2e\x40\x3e\x98\x77\x2d\x25\x5e\xd3\x0d\xdb\x25\xff\xed\xd1\xdc\x9e\x47\x9b\x7f\x71\xdc\x07\xaf\x52\x12\x7e\xe5\xf6\x4d\x33\x7e\x84\x9b\x98\xf2\x19\x19\xea\xe7\x18\x73\x38\xdc\x96\xcc\x09\x19\xf2\xa2\x41\xa3\xca\x6b\x9c\x1e\x4e\xbd\xc8\x19\x91\x71\xc9\xf0\x2e\x25\xd1\xf3\x4e\x11\xe3\x54\xa8\x62\x7c\x0e\xff\x59\x9e\x18\x78\x20\x34\x44\x9f\xdd\x30\x95\x37\x15\x4a\x7b\xe4\xe9\x2d\x90\xb7\xbb\xc7\x2d\xfb\xb2\xff\x0f\x6f\xa2\xba\x14\x37\x84\x93\x4f\xf4\x96\x86\x0e\x13\x7f\xfa\x4f\x83\xfa\x3e\x3a\x5f\x9f\xaf\x9f\xaf\x3f\xf9\xbd\xd8\xcd\xfe\x61\xc6\x46\x32\xd4\x26\x57\x1a\x4f\x62\xcb\x68\x7e\x93\x29\x79\x1a\x53\xad\xea\x1a\xf5\x69\x7a\xfa\xa7\xfd\x53\xb8\xfa\xf3\xe2\x24\xae\x36\x32\x9d\xc4\x33\x7c\xbf\x9f\xf2\x25\x71\xb8\xad\x49\xe2\xf0\xbf\x40\xfe\x08\x00\x00\xff\xff\x2b\xde\x8c\x56\x16\x22\x00\x00") +var _staticIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x5a\x6d\x6f\xdc\xb8\x11\xfe\x7e\xbf\x82\xc7\x62\x0f\x09\x70\x5a\x39\x35\x0e\x28\x1c\x69\xd1\x34\xce\x35\x06\x2e\xc9\x21\x71\x0b\xf4\xd3\x82\x12\x67\x25\xc6\x14\xa9\x92\xa3\x5d\xbb\x45\xff\x7b\x41\x52\xd2\xea\x6d\x73\x59\x27\x01\x02\x04\x1b\x91\x9c\x37\x3e\x1c\x0e\x67\x48\x27\x3f\x72\x9d\xe3\x43\x0d\xa4\xc4\x4a\x6e\x7e\x48\xdc\x7f\x44\x32\x55\xa4\x14\x14\xdd\xfc\x40\x48\x52\x02\xe3\xee\x83\x90\xa4\x02\x64\x24\x2f\x99\xb1\x80\x29\x6d\x70\x17\xfd\x85\x0e\x87\x14\xab\x20\xa5\x7b\x01\x87\x5a\x1b\xa4\x24\xd7\x0a\x41\x61\x4a\x0f\x82\x63\x99\x72\xd8\x8b\x1c\x22\xdf\xf8\x99\x08\x25\x50\x30\x19\xd9\x9c\x49\x48\x9f\xfd\x4c\x6c\x69\x84\xba\x8b\x50\x47\x3b\x81\xa9\xd2\x0b\xa2\x39\xd8\xdc\x88\x1a\x85\x56\x03\xe9\x7f\x17\x68\x74\x76\x45\x7e\x6f\x10\x85\x2a\x08\x96\x40\xde\xd5\xa0\xc8\x07\xdd\x98\x1c\x88\x50\xe4\xdd\x87\x9b\xb7\xb7\x0b\x02\x59\x83\xa5\x36\x03\x59\x6f\x44\x5e\x32\x90\xe4\x35\x28\x23\xee\x2c\x28\xf2\xe4\xaf\x95\xc8\xcb\xae\xf9\x94\x6e\x7e\x08\x52\x50\xa0\x84\x4d\xd0\x9d\xc4\xa1\xd5\x0e\x49\xa1\xee\x48\x69\x60\x97\xd2\xd8\xe2\x83\x04\x5b\x02\xa0\x8d\x33\xad\xd1\xa2\x61\xf5\x3a\xb7\x96\x12\x03\x32\xa5\xc7\xf1\xce\xbc\x53\xdc\xba\x06\x25\x72\xad\x44\xfe\x28\xf6\x52\x14\xa5\x14\x45\x89\x8f\xe2\x66\x75\x2d\x45\xce\x1c\xf2\xa7\xf9\x93\x38\x38\x8b\xfb\xcc\x34\x7f\xe8\xf0\x50\x6c\x4f\x72\xc9\xac\x4d\xa9\x62\xfb\x8c\x19\x12\xfe\x8b\xe0\xbe\x66\x8a\x47\x15\xef\x3a\xbc\x81\x24\x2b\xc2\x47\x6b\x14\x21\x09\x17\xbd\x04\xb7\x54\x4c\x28\x30\xfd\x28\x21\x09\x1b\xcb\x8f\x32\xc3\x14\xa7\xdd\x44\x86\x94\xa2\x2a\x88\x35\x79\x4a\x63\x51\xb1\x02\x6c\x5c\xe8\xba\x04\xb3\x75\x96\xaf\x6b\x55\x50\x12\x9c\x95\x5e\x5e\x50\x52\x82\x33\x23\xa5\x7f\xbe\xa0\x9d\x02\x1e\x09\x25\x85\x82\x28\x93\x3a\xbf\xa3\x84\x49\x4c\xe9\x40\x41\xe7\x10\x6c\xa0\x33\x6b\x10\xb5\x9a\x98\x88\xba\x28\x24\x18\x4a\xdc\xfe\x4b\x69\xa0\xa1\x84\x33\x64\xed\x98\x9b\xab\x94\xac\xb6\x40\x09\x33\x82\xb5\x70\x01\x4f\xe9\x8e\xc9\xbe\x57\xb2\xcc\xad\xc5\xad\xe7\x71\x40\x8a\xc2\xaf\xd3\xc0\x28\x42\x12\x5b\xb3\x13\x16\x44\xce\xa9\xe8\x26\x89\x1d\xc9\xc0\xea\x38\x98\xd4\xaf\x41\xcc\xc5\xbe\xf5\x92\x58\xb1\x7d\xb7\xb8\x15\x13\x8a\x18\xed\xcc\x75\x9f\xf4\xf4\x3a\x25\x99\x21\xf1\x68\x49\x05\x77\x3e\xc4\xd0\x6e\x17\x57\x75\xb0\xea\xb5\xd1\x85\x01\xe7\x78\xde\xe7\x52\x1a\x96\xe6\x8a\x5c\x5e\xd4\xf7\xcf\xc7\x53\x5d\x60\x8b\x9c\xd3\x0d\x1b\x91\x45\x23\x6a\xe0\xe3\x4e\xa6\x44\xc5\x10\x38\x6d\x27\xd4\x0d\x66\xcc\x50\x6f\x6c\xd7\xb1\xf5\x3d\xad\x29\xde\x61\xae\xc8\xb3\x8b\x8b\xd5\xf3\x76\x4d\xf6\x4c\x36\xa0\xf4\x21\xa5\xcf\x2e\x2e\x86\x7d\x95\x50\x29\x1d\xf7\xb0\xfb\x40\xb5\xb9\x09\x11\x51\xfc\x47\xa8\x62\xbd\x5e\x0f\x00\x9f\xe0\x3f\x03\x73\x3c\x69\xa3\x0f\x27\x01\xc9\xb5\x8c\x6c\x35\x1a\x9e\x10\x30\xc3\x09\xc2\x3d\x46\x39\x28\x84\x76\xde\xae\x77\xbb\x13\x8a\x0b\x55\xd8\x09\xf7\x9c\x3f\x72\x9b\x7f\x46\xe5\xce\x92\xcb\x11\x99\x0f\x9a\x0b\x0a\xb6\x1e\x18\xba\xb9\x48\xe2\xf2\x72\x41\x4c\x3d\x96\x02\xf7\xb8\x24\xc4\x1d\x16\x74\xf3\x6b\xdb\x4c\xe2\x7a\x66\xf6\x18\xd1\xc5\xae\x79\xc7\x57\x03\x53\xc2\xb7\x44\x52\xc2\x97\xc2\xe8\x24\x74\x18\x4a\xf8\xee\x00\xcc\x75\x55\x09\xfc\x76\x10\xb6\xf2\xbf\x08\xc4\x4e\x46\x80\xf1\x65\x68\x7d\x6f\x40\x1a\xa8\xb5\x15\xa8\x8d\xf8\x86\x0e\x39\x54\xf2\x45\x90\x8e\x04\x05\x5c\xdf\x0f\xba\xbe\x37\x70\x91\x99\x02\xbe\xa1\x97\xb6\xf2\xbf\x08\xd2\x4e\x46\x40\xf3\x36\xb4\xbe\x37\x20\x79\x63\xe6\x59\xcd\xd7\x44\xb2\x53\xd0\x43\x79\x71\xe5\xff\x3d\x06\xd1\x5e\x56\x80\xf4\xba\x6d\x7e\x1d\x4c\x47\xcd\xb6\x31\xce\xb0\xba\x96\x85\xdc\xa9\x0d\x99\x0b\x2b\x60\xe9\x04\x4f\xc6\xb3\xeb\x8e\xcb\xa1\x7a\xa1\xea\x06\xbb\xe9\xee\xb4\xa9\x22\x97\xad\x19\x2d\xc9\xb0\x11\xd9\x8a\xec\xa4\x66\x18\x19\x9f\xbb\xb7\x79\x6d\x40\xa6\x96\x2c\x87\x52\x4b\x0e\x26\xa5\x1f\x80\x99\xbc\x5c\xaf\xd7\x01\xb1\xfe\xc0\xb6\xbe\x7f\x68\x9b\x87\xfe\xd8\x44\x96\x49\xe8\x0c\x09\x0d\xff\xeb\x54\x87\x8f\x52\xef\xc1\x74\x9d\x21\xc3\x0b\x4a\x7c\xd7\x72\x06\x93\xe0\xb1\xc4\x3d\xf6\x99\xd9\x4a\x61\x49\x6c\xae\xeb\x90\x96\xd3\xa1\x4f\xb3\x3c\x78\xe6\x8b\x3c\xac\x32\x96\x67\x30\xd7\x0c\x4b\xba\xf9\x9d\x61\x79\x26\x63\x38\x5c\xba\x63\xe5\x4c\xe6\x3e\x8c\x3e\x0c\xe2\xe7\xc3\x5c\x48\x12\x8f\x91\x70\x14\x13\xb4\x12\x0c\xb5\xde\x88\x68\xdc\x95\xc4\x1e\xff\xa3\xd3\xb6\x9e\xd9\x95\x13\xae\x70\xd8\x24\x3f\x46\x11\x89\xd7\x7d\x25\x40\xa2\xa8\xab\x31\x76\x5a\x23\x98\x4f\x56\x83\xc3\xb0\x11\xbe\xab\xc6\x65\xf2\xa3\x22\x31\xd4\x83\x25\x62\x6d\xaf\xe2\xb8\x10\x58\x36\xd9\x3a\xd7\x55\x3c\x2c\xf1\x5d\xbf\xd1\x19\x25\x21\x2e\xa6\x74\x9b\x49\xa6\xee\xe8\xe6\x58\xda\x11\x61\x09\x73\xa5\xc3\x47\xc8\x91\x64\x0f\x63\xd9\x57\xf1\x48\x9e\x53\x30\x17\x36\xbb\x68\xf0\x72\x7f\xaa\x04\xe7\x1a\x9f\x9f\x6b\x6c\x2c\xac\x6d\xc0\xc6\x0a\x0e\x73\x55\x6e\x7d\x0d\x12\xa6\x88\xa7\x1a\xd4\xa6\xa3\x9a\xae\x03\x39\x34\xc3\x45\xcb\x60\x13\xc7\x08\x55\x2d\x19\xb6\x31\xb3\x6b\x75\x7b\xea\x58\xe5\x21\x5f\xda\x1b\xc7\x65\x58\x11\xb1\x23\x4f\xc2\x5e\x21\x69\x4a\xe8\x1b\xcd\xc5\xee\x81\x3e\x25\xff\x25\xab\x93\x35\x6b\xc6\x78\x01\xc4\xff\x46\xb5\x11\x15\x73\x9e\xfb\xe6\xdd\xf5\xcd\xaf\xff\x9a\x55\xae\x2b\xf2\x3f\x02\xd2\xc2\x54\xd1\x8d\xb2\x60\xf0\x0c\x45\xb6\xc9\x73\x57\x74\x6e\x5e\xbe\x7f\xf5\xe2\xf6\xd5\x67\x2b\xba\x06\x09\x08\x67\x28\xe2\x4c\x15\xae\xf6\xbd\x7e\xf5\xdb\xab\x13\x7a\x56\xc7\x45\x43\x7e\x02\xec\x10\x4b\x92\x5c\x73\x58\xf0\xfb\x3f\xd1\x4d\xb2\x4a\x09\x96\xc2\xae\x5d\xe4\x66\x88\xc0\x5d\x6e\xef\x82\xcf\x93\xa7\x64\xb5\x19\xb9\x86\x97\xf2\x09\x65\x5d\xfc\x09\xea\x7a\x2d\xc9\x2a\x22\x21\x24\xfd\xc3\x48\xb2\xda\xb4\x57\x45\x4a\xeb\x1a\xdc\x3e\x55\xda\xc0\x0e\x8c\xbf\xf9\x98\x38\x6a\x6f\x5d\xa5\x39\xc8\xb5\x2d\xb5\xc1\x20\xea\x35\xb3\x47\x0b\x8f\xa6\x95\x27\x4c\x1b\x46\xb7\x91\x61\xc7\x50\xf7\x08\xe3\x86\xec\xef\x0e\x8e\x7c\xb5\x89\xc7\xdd\x6f\x59\x05\xbd\x95\x9d\x79\x49\x1c\x36\xd3\x63\xb7\xd6\xb6\xd2\x9c\xc9\xc5\xcb\x30\x3f\x12\xb9\x88\x3c\xbe\x39\x29\x7f\x19\x53\x84\x64\xc7\xcf\xe1\xfa\x78\x87\xea\x2d\x2d\x7f\x99\xdf\x54\x8d\xaf\xa4\x3a\x60\xa5\xb6\xd0\x5e\x50\x71\x61\x2b\xd1\x8b\x1f\x5f\x44\xbd\xf4\x74\x73\xb7\xf7\x34\xa5\xe0\x1c\x54\x4a\xd1\xb8\x1c\xeb\x27\x14\x15\xd8\xe7\x67\x5c\x3d\x2d\x4d\x7f\x92\xf0\xb5\x01\xc6\x3b\x92\xb0\xb7\x60\xf1\x3d\x38\x38\xf9\x93\xa7\xf3\x0d\x39\x10\xc6\x24\xb8\x28\xe9\x7e\xa3\x03\x33\xca\x05\xb5\xf6\x1e\xc8\x77\xd2\x4d\x62\xd1\x68\x55\x6c\xde\x6a\x14\x39\x5c\x25\x71\xdb\x26\xb7\xa5\xb0\xc4\x55\xcc\x44\x6a\x7d\x67\x09\x6a\x92\x01\x41\xb0\xfe\x3e\xda\x04\xf5\xb3\x0b\x9d\xd1\xae\x1e\xdb\x92\xa1\x8a\x0a\xa3\x9b\x9a\xf4\x5f\xd3\xfc\x6a\x34\x8d\xc5\x75\x1b\x24\x57\xdb\xbd\x80\xc3\xd6\xb0\x03\x1d\x68\xf0\xb2\x2d\xe4\x5a\x71\x1f\x4d\xdf\xb3\xc3\x14\xf9\x33\x84\x97\x70\xcf\x9b\xaa\xfe\x94\x82\xd7\x70\x4f\x1c\xcd\x5c\xcb\x14\x9a\x51\xa6\xd7\xaa\x89\x2a\x40\x16\xf9\x91\x49\xfe\x66\xa6\xc9\x5b\xe9\xf3\xa9\xab\x85\x74\x06\x79\x17\xaf\xda\xb5\x5b\xde\xd6\xfd\xd2\xc6\xcb\x74\xfd\x3e\xef\xc9\x56\x11\xe9\x42\xa9\x1f\x98\x45\x4f\xb2\x94\x4d\x2d\x99\xfe\xc2\xbf\x49\x9c\x32\xbe\x8f\xae\x81\xcc\xeb\x7a\x84\x92\x37\x60\x2d\x2b\x60\x59\xcb\x31\xd7\x57\x18\x09\x64\x52\xe4\x83\xe0\x8c\xa6\x51\xb9\x73\xe8\x60\x47\x2b\xa9\x8d\xce\x8f\x30\xe5\xe6\xfa\xc4\x5c\xa7\xd9\x6c\x80\x74\x15\x91\x1b\x7e\x84\x78\x4a\xd4\x3a\xeb\xd0\x3d\x05\xdf\xe6\x52\xd4\x99\x66\x86\xcf\xdc\x53\x37\xe8\xaf\xf3\x7b\x37\x0d\x4e\x5b\xb5\x81\xae\x67\xf4\x25\x5e\x38\x44\xbc\x7a\x17\x0d\x06\x87\xb9\x16\x44\x8b\x23\x75\x7f\x9d\xbe\xb4\xa1\xc6\x47\xf8\x1c\xa7\x49\xb6\xec\xc2\xf9\xc9\xbb\xde\x59\xb1\xec\x43\xa2\xbf\xbd\xdb\xda\x5a\x28\x05\x66\xf1\x6e\xbd\x7d\x09\x69\xa5\xb4\x94\x74\xfc\x32\xd2\xf6\xae\x0b\xb1\x6b\xdf\x39\x7e\xd3\xcc\x21\x1a\x42\x5d\xfb\x66\x66\xfb\x42\x6e\xae\x9a\x0e\xcd\x76\x45\xf3\xe6\x94\x84\x51\x69\x3c\x8d\x06\xdd\x53\xc1\x40\x41\xc7\x7a\x6a\x72\xb5\x81\x53\x2c\x6e\x6d\x6a\x03\x7f\x44\xde\xc5\xb3\x29\xf5\x52\xf9\x3d\x5f\x97\x70\x32\x85\x94\xfa\xf4\x43\xcc\xb1\x4e\x21\x83\xbd\x16\xbe\x0f\xfe\x81\xa3\x7b\x08\x5b\x70\x36\x3f\x92\x35\x32\xeb\x9d\x8d\xdc\x8a\xfa\x8a\xfc\xcd\xe8\x83\x05\xd2\xd5\xba\xae\x3c\x69\x6c\xf7\x2e\xba\x20\x87\x19\xa3\x0f\x91\x84\x1d\x1e\x05\x31\xc5\x4f\x93\xb6\xe7\x4f\x4f\xeb\x3a\xc9\x1d\x3c\xd8\xf5\xf4\x20\x1f\x24\x9f\x5d\x80\xfc\x64\xde\xb5\x94\x78\x4d\x37\x6c\x97\xfc\xb7\x47\x73\x7b\x1e\x6d\xfe\x29\xe0\x10\xbc\x4a\xab\x60\xfa\x31\x62\x15\x80\xaf\xb5\x45\x17\xb4\xdb\x30\xd5\xee\x4d\x76\xd2\xd4\xcf\xc8\x60\x3f\xc7\xd8\xe3\xe1\xb7\x64\x6e\xc8\xa0\x3f\xcb\xe0\xe5\x92\x6d\x9c\x57\x4e\xdd\xcf\x59\x97\x09\xc5\xe1\x3e\xa5\xd1\xb3\xce\x02\x2e\x98\xd4\xc5\xf8\x00\xff\xa3\x04\x33\xf0\x90\xd0\x90\x7d\x5a\xc4\x75\xde\x54\xa0\xf0\xc4\x9b\x5d\x20\x6f\xb7\x9d\xf3\x97\xe5\x8d\x33\xbc\xc2\xea\x72\xe3\x10\x87\x3e\xb2\x3d\x0b\x1d\x36\xfe\xf8\xef\x06\xcc\x43\x74\xb9\xbe\x5c\x3f\x5b\x7f\xf4\x9b\xb8\x9b\xfd\xa7\x19\x1b\xc5\xc1\xd8\x5c\x1b\x38\x8b\x2d\x63\xf9\x5d\xa6\xd5\x79\x4c\xb5\xae\x6b\x30\xe7\xe9\xe9\xff\x26\xe0\x1c\xae\xfe\xa0\x39\x8b\xab\x0d\x69\x67\xf1\x0c\x1f\xfe\xa7\x7c\x49\x1c\xae\x79\x92\x38\xfc\xf9\xc8\xff\x03\x00\x00\xff\xff\xa9\xd9\x97\x42\x4f\x22\x00\x00") func staticIndexHtmlBytes() ([]byte, error) { return bindataRead( @@ -289,32 +276,12 @@ func staticIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/index.html", size: 8726, mode: os.FileMode(420), modTime: time.Unix(1528532867, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - -var _staticJavascriptsDs_store = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x98\xc1\x4a\xf3\x40\x14\x46\xcf\xe4\x2f\x3f\x29\x8a\x06\x5c\xe8\x32\x4b\x37\x16\x8a\x4b\x37\xa1\xd4\x85\x6b\x1f\x40\xdb\x2a\xb6\xb5\x38\xb5\x4d\x11\x77\xdd\xf8\x0e\xbe\x82\xcf\xe7\x03\x28\x99\xb9\x62\x4c\x27\x4b\x31\xca\x3d\x10\x0e\xb4\xf9\xee\x0c\x49\x49\xef\x0d\x60\x7a\xab\xab\x2e\x24\x40\x8c\x77\xd4\x26\x48\x2c\xc7\x06\x91\xf8\x7f\x51\xcf\xd5\x98\x30\x62\x40\xce\x04\x1b\xae\xa5\x34\x8c\xe2\xde\xed\x30\x60\xce\x9c\xd9\x97\xfb\x77\x47\x87\x29\xcb\xb3\x99\x1d\x0d\x67\x76\x28\x3f\x92\x13\xe0\xf0\xcd\xe1\xf2\xdb\x0c\xb1\x58\x72\x96\xe4\x2c\x5c\xa5\x50\xee\x65\x23\x37\x66\xc2\x0d\x63\xb7\x6a\xe1\x3c\x94\x33\xaf\x95\xdc\x5e\x20\x77\xc1\x03\x96\x05\xb7\x5c\xb3\xa8\xdd\xf5\x73\xa9\xca\x2e\x53\xee\x59\xb9\xf3\x1f\x39\xe2\x98\x8e\x3b\xba\xc1\x3d\xec\x57\xf6\xd0\x66\x8e\x75\x57\xac\x66\x3d\xf3\x54\x49\x28\x8a\xa2\x34\x0b\xe3\x15\x6f\xfd\xf4\x46\x14\x45\x69\x1c\xc5\xf3\x21\x15\x67\xe2\xb5\xb7\x91\xef\x23\x71\xab\x94\x49\xc4\xa9\x38\x13\xaf\xbd\x8d\x9c\x17\x89\x5b\xe2\x58\x9c\x88\x53\x71\x26\x5e\x7b\xcb\x43\xcb\xc8\xf0\x61\x64\x65\x23\x13\x8a\x49\xc4\xa9\x38\xfb\x9e\x6b\xa3\x28\xbf\x9d\x7f\x5e\x49\xf1\xff\x7f\x5a\x3f\xff\x2b\x8a\xf2\x87\x31\xad\xfe\x79\xbf\xf7\x39\x10\x6c\x10\x49\x23\x70\xf9\x11\xa8\x34\x02\x94\x9a\x80\xc8\xbf\x2c\x3c\x28\x7d\xae\x8d\x80\xa2\x34\x8c\xf7\x00\x00\x00\xff\xff\x74\x65\x5d\x65\x04\x18\x00\x00") - -func staticJavascriptsDs_storeBytes() ([]byte, error) { - return bindataRead( - _staticJavascriptsDs_store, - "static/javascripts/.DS_Store", - ) -} - -func staticJavascriptsDs_store() (*asset, error) { - bytes, err := staticJavascriptsDs_storeBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "static/javascripts/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1520798491, 0)} + info := bindataFileInfo{name: "static/index.html", size: 8783, mode: os.FileMode(420), modTime: time.Unix(1583787593, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _staticJavascriptsApplicationJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x5a\xef\x72\xdb\x38\x92\xff\x9e\xa7\xc0\x62\xbc\x67\x32\xa6\x28\xd9\x77\x99\x9d\xa1\xa3\xe4\x3c\xf9\xeb\xab\x99\x4c\x2a\xc9\xdc\x55\x9d\xed\xf5\x41\x64\x4b\xc2\x98\x02\x59\x00\x64\xc9\x89\x75\xb5\x4f\xb3\x0f\xb6\x4f\x72\x05\x10\x20\x01\x92\x92\xe5\xf9\x72\x3b\x35\xe5\x88\x40\xf7\xaf\x1b\x0d\xa0\xff\x91\xb7\x84\xa3\xcf\x92\x48\x81\xc6\xe8\x27\x92\xde\x4c\x0a\x06\xf1\x2f\x45\x06\x79\x0c\x6b\x09\x2c\x0b\xbe\x3d\x41\x68\xc9\xf3\x04\xe1\xa1\x50\x84\x38\x7a\x82\x50\x06\x53\xb2\xcc\xa5\x48\x90\x9a\x46\x08\x2b\x8c\xa5\xc0\x09\x32\xff\x61\xca\xa8\xa4\x24\xa7\x5f\x29\x9b\x69\x96\x8a\x88\x4b\xc8\xce\xa4\xa1\x63\xcb\x3c\x37\x53\x6f\x29\xa3\x62\xde\xcc\x39\x53\x1f\x79\x31\xe3\x20\x6a\xf0\x91\x19\xff\x42\xf8\x0c\x64\x23\xd3\x8e\x7f\x82\xb2\x10\x54\x16\x9c\x82\x9e\xb4\xe3\xaf\x8a\xc5\x82\xf6\xd0\xbf\xa5\x39\x38\x9a\x3b\xe3\x2c\xa3\x6c\xe6\xcb\xdd\xa8\x3f\x54\x58\x75\x13\x34\x5d\xb2\x54\xd2\x82\x05\xa1\x31\x05\x07\xb9\xe4\x0c\xc9\x39\x15\xf1\x0c\x64\x60\x4d\x13\xa2\xf1\x78\x8c\xf0\xd4\x70\xe2\x53\x8b\x96\x2d\x39\x51\x08\x3d\x58\x74\x8a\x02\x0f\xc8\x98\xaf\xc2\x52\x36\xb2\x94\xb5\x5c\x3c\x1a\x25\xfa\x7f\x2d\x00\xa1\x8d\xfe\xab\xb6\x19\x58\x76\x5a\x3f\x08\x85\x85\xc6\xe8\x35\x91\x10\x97\x84\x0b\xe8\x17\x14\x9e\xfa\x8a\x34\x4b\x0f\xc2\x46\x36\xb0\x6c\x1b\x96\xb3\xb1\x16\x6c\x83\x20\x17\xd0\xc7\xcc\x8a\x55\x10\xb6\xf5\x5e\xd0\x3c\xa7\x02\x21\x34\xd6\xa4\x83\x4a\x77\x67\x29\x90\x16\x2c\x13\x6a\xfe\x17\x22\xe7\xf1\x34\x2f\x0a\x1e\x18\xae\x21\x3a\x1e\x8d\x46\x61\x43\xad\x8c\xa6\x64\xa1\x31\x62\xb0\xd2\x62\x03\x6d\xc8\x8a\xc4\x4e\xc7\x02\xe4\xe7\x0a\x38\x30\x02\x0c\x85\xb1\x73\x4d\x28\x8b\xf3\xcf\xbf\x7e\x96\x9c\xb2\x59\x10\xc6\x62\x39\x11\x92\x07\xc7\xc7\x11\xfa\x21\x34\x5b\xbc\x09\x4f\x9f\xac\x28\xcb\x8a\x55\x2c\xcc\x55\x53\xa2\xf5\xb5\x3b\x7d\xf2\x44\x69\x65\xce\xda\xce\x4b\x48\xb3\x33\x29\x39\x9d\x2c\x25\x24\x08\x9f\x67\xfa\x56\x49\x10\x52\x1d\xe0\x73\x96\xd1\x94\xc8\x82\x8b\x04\x5d\x60\x35\x8a\x23\x84\xaf\x45\x09\xa9\xfa\x31\xa5\x6b\xb9\xe4\xa0\x7e\x2e\x8a\xf4\x46\xfd\x2b\xe4\x72\xa2\xa7\xc8\x8d\x1e\xcf\x60\x51\xe8\x71\xb2\x28\x73\xc0\x57\x0a\x5d\xcc\x0b\x2e\xab\x7b\xf3\x9e\x88\xf9\x3e\xa7\xbd\xa1\xc6\xb5\x35\x46\x11\xfa\x4b\x58\x9f\x77\xc9\xe9\x62\x01\x59\x45\xf8\x0b\x08\x41\x66\xd0\x83\xac\xb7\xbe\x9a\x45\xe3\x8e\x00\xc3\xa7\x64\x94\x39\x95\x01\x1e\xa8\xff\xde\x7c\x78\x8d\x3e\xbe\xfb\x88\x3e\x9f\xbf\xfb\x70\xf6\xe5\xb7\x4f\x6f\xf4\x28\x8e\xd0\x49\x18\x97\x45\x19\xf8\x5b\x68\xd0\x63\x0e\x65\x4e\x52\x08\x86\x7f\xbd\x14\x97\xe2\xe9\x30\x42\x18\x87\xcd\xa8\x1e\x3c\xa8\x46\x1b\x0f\xf0\x05\x84\xfc\x04\x39\x91\xbd\x4e\x40\x29\x5f\x12\x39\xf7\x34\x57\xfb\xf4\x91\x48\x65\x18\x59\xfc\x5c\xac\x80\xbf\x22\x02\xac\x52\xd3\x82\xa3\x40\xf1\x51\x34\x46\xa3\x53\x44\xd1\xf3\x8a\xb7\xbb\xc5\x71\x0e\x6c\x26\xe7\xa7\x88\x1e\x1d\x35\x97\x50\xdd\x51\x25\x33\xa6\x2c\x83\xf5\xaf\xd3\x60\x0b\xf7\x05\xbd\x0a\xd1\x0b\x34\x38\x6e\x58\x9b\x7d\xe4\x4b\x38\x35\x83\x1b\xe7\x1e\x9a\xe9\x29\xc9\x05\xd4\x1b\x39\xa5\x39\xbc\x2a\x98\x04\x26\xc5\x6f\x2a\x42\x6c\x3b\x1d\x17\x78\x38\xd5\x4e\x36\x72\xac\x51\xbb\xe9\xbb\x5f\x57\x0c\x38\x0e\xfb\x27\x3f\x90\x05\xf8\x73\xee\x09\x8b\x7a\xcd\x7b\x15\xff\x5e\x50\x16\xe0\x21\x0e\x7b\x95\x75\x34\x4d\x49\x9e\x4f\x48\x7a\x13\x21\xe0\xbc\xe0\x56\xf1\x83\x98\xfc\x4e\xd6\x81\xb5\x8f\x8e\x7f\x5a\x52\x6b\xcd\x41\x18\x19\x12\xb1\x4c\x53\x10\x22\x41\x35\xa2\x75\x6f\x0a\x37\xa9\xfe\xa9\x2c\xea\xfa\x05\xf7\xf6\x7b\x31\xf8\x55\x91\xe7\xa0\x75\xec\x09\xc4\x53\x1b\x9a\x94\x90\x85\x72\x14\x89\x05\x31\xb0\xc6\xdf\x4c\x1b\x64\xe5\x72\xac\xa0\xc0\x4a\xd6\x3e\xe8\x3f\x29\xac\x5c\xd1\xea\xd9\x77\x3c\x89\x72\x17\x44\x8a\xeb\xb4\x60\x92\x50\xb5\x5d\x8e\x64\x3d\xa5\x9e\xcb\x22\xcf\x29\x9b\x7d\xa1\xe9\x0d\xf0\xa4\x8e\xe1\x36\xc0\xb5\xc7\x0d\xf9\x39\x93\xc0\x6f\x49\x9e\xa0\x67\x23\x1d\x63\xeb\xd4\xa1\xcf\x2d\xe8\x5d\xc8\xa9\x90\xc0\xbe\x14\xd5\x11\xd7\x6a\x44\x08\xa7\x73\xc2\x66\x60\x0f\x19\x07\x96\x01\x0f\x1b\x26\x1d\x36\x5e\x7b\xba\xd8\xbb\xd7\xcc\x7f\xac\x74\x0a\x9a\x83\x53\xe1\xec\x0a\xce\x5a\xfe\x96\xc8\x68\x90\x8b\xd2\x03\xf6\x66\xfa\x55\xda\xf4\xc9\x98\x13\xf1\x4a\x2f\x32\x0b\x9a\xb4\xa8\x2d\x6d\x59\x66\x44\x82\x9d\xde\x1b\xaf\x4e\x77\xfa\xf1\xdc\xa3\xb3\x27\x9e\xba\xf1\xdb\xc0\x72\xd8\x1f\xc9\x26\x6e\xfd\x58\x66\x76\x6f\x34\x2f\x3d\xec\x87\x74\x49\xf6\xc6\xb5\xe9\x68\x3f\xa4\x99\x75\xd1\xf4\xe9\x72\x0f\xdd\xb6\xd3\xee\x5d\x2b\x34\x46\x02\xa4\xbd\x33\x41\x87\x03\x55\xd7\x51\xdf\xe1\x4a\xc9\x29\xc8\x74\x5e\x0b\x8e\x3c\x4c\x8b\xd3\x1c\x77\xe7\xac\xee\x3a\xf3\xbe\x4e\x7f\xea\x64\xa3\x69\x0e\x84\xd7\x5a\x76\x59\x7a\xed\xf0\xba\xe5\x28\xfa\xcd\xe1\x53\x3d\xc6\x1e\xd5\x56\x58\xfe\x20\xb4\x16\xa9\x53\xc4\xda\x02\xfb\x69\xd2\xc6\x6b\xe5\xca\xbe\xdf\xdb\xcf\x48\x3e\x4f\xdb\x4a\xbe\xc0\x1e\xb5\x0e\x02\xfc\x5d\x4a\x78\x76\x6d\x71\xae\x6f\x49\xbe\x54\x69\x92\x84\xb5\x74\x8f\x6e\x56\x6b\xdd\xac\xdc\xf7\x1c\x5b\xd2\x1a\xa1\xcb\x19\x9b\xd8\x54\x4f\x5f\x8a\xf7\xcb\x05\xa9\x2d\x70\x10\x60\x49\x65\x5e\x8b\xc5\xef\xa8\xe4\xc5\x24\x41\x18\x1d\x19\xfe\x86\xf2\xbb\xd2\xc8\xbb\x9e\x10\x6e\x39\x0c\x51\x9c\x0a\x11\xe0\x15\xcd\xe4\xdc\xba\xf5\x4a\x7b\x1d\xf0\x1b\x0f\x88\x8e\x10\xfe\x33\x6e\xdb\x7f\x97\x5f\xee\x11\xcc\x61\x51\xdc\xc2\xab\x9c\x28\x99\x76\x6e\x30\x21\x7c\x40\x18\x5d\xa8\x44\x0f\x79\xa3\x42\x72\x5a\x42\x86\x5b\x5a\xe2\xe3\xd1\xa8\xd6\xa5\xb5\x73\xd6\x89\xee\xda\x39\x1b\xb2\xeb\x9d\x9b\xd3\x0c\x82\xee\x06\xda\xea\xca\x38\x6d\x9d\x52\xa6\x24\x07\x5b\x8a\x84\xf1\x94\x64\x70\xce\x02\x3c\x25\x42\xe2\xf6\x2e\x6b\x17\xbc\x5b\x8f\x1c\xf6\x55\x42\x7b\xfa\xc7\x6a\x60\x1c\xf7\x2e\x1d\xd2\x8a\x64\x2f\x2d\xea\x28\xf1\x58\x3d\x5c\x6f\xbf\x4b\x19\xee\xd0\xed\xa5\x91\x1f\x69\x1e\xab\x96\x89\x18\xbb\x34\x92\x15\xc9\x5e\xca\xd4\xe1\x69\x7f\x3d\xbc\xbb\xbd\xd3\x1b\x54\x87\x5d\xac\xa8\x8a\x34\x6d\xc9\xb6\xff\x61\xd9\x52\x22\xa0\xd5\x1f\x4a\x1c\x57\xad\x7d\x0b\x3e\x77\xa7\x6d\xca\x34\xe1\x40\x6e\x4e\x1d\x90\x19\x91\x73\xe0\xfd\x08\xef\xec\x1c\x72\x37\x6e\x3b\x16\x61\x24\xbf\xdb\xa2\xcd\x99\x9d\xf3\xb1\xb6\x41\xd5\x3d\x9e\x2e\xd2\x5b\xb7\xfd\xd3\x62\x36\x4d\xb5\x2e\xd3\x6f\xec\x86\x15\x2b\xd6\xc7\xe3\x95\x67\x86\xe3\x08\x61\x14\x28\x57\xab\x7b\x31\xe7\xac\x7b\x18\xdc\xdc\x51\xb9\xce\xb0\xea\x46\x75\x3a\x15\xa6\x32\xa8\xbb\x15\xea\x39\xf8\xa6\x72\x7e\x75\x06\xdb\x25\x41\xd8\x2e\x68\x1e\x2a\x2c\x24\x99\xa9\xf2\x2e\x41\x58\x56\x05\x05\xdc\x56\xe5\x99\x69\x2a\xa6\x39\x4d\x6f\x90\xcc\xe2\xb4\xc8\x07\xba\x9c\x26\x58\x95\x22\xf3\x62\x65\x24\xe0\xba\x27\x27\x61\x51\xaa\x6a\x3c\x41\xd7\xb1\xfd\x1d\x28\x2d\xed\x83\x75\xac\xea\x9e\xc8\x45\x1e\x84\xe1\xce\xec\x5e\x9b\xec\x40\xe5\x78\x8a\xd8\x94\xd2\x06\xd6\x31\x27\xb1\x2d\x19\x11\x86\x71\x46\x24\x09\xb0\x95\xe3\x06\xac\x6d\xa1\xc9\xe9\x22\x74\xaa\x06\x25\x9c\x64\x99\x09\x48\xaa\x8e\x1f\xf0\x8a\x14\x87\x3d\x9b\xaf\x78\x9a\x6a\xb7\xe0\x0b\x22\x25\x64\xb6\x28\xde\x76\x7d\xcb\x9c\x4a\x81\x74\x73\xad\xcf\xad\x9b\x76\x85\xe9\xb1\x0c\xb1\xd3\x48\x53\xf1\x81\x91\x05\x28\xd6\x0a\xc6\x6d\xb1\x28\x8a\x8c\x72\x48\x55\x0d\x6f\xc1\x21\xcf\x69\x29\xa8\xa0\x5f\x21\x30\x2c\x75\xa1\x1e\xa1\xef\x47\x11\x3a\x79\xe6\x58\xca\xe1\x1f\x8f\x11\xc6\xdd\x5e\xe7\x73\x21\x79\xc1\x66\x2f\xd4\x61\xbf\x8e\x41\xa4\xa4\x84\xc0\x2a\xa6\x8f\xf6\xf3\xa1\x25\xe9\x31\x59\xcd\x52\x4b\xd2\x3c\x43\xac\x39\x1f\x89\xad\xed\xee\xac\xd0\xb1\xb8\x90\x3c\x42\x0b\xca\x7e\xd6\x6d\x9b\x08\x41\x36\x83\xea\xb7\x5d\x92\x90\x2a\x91\x35\x1e\x59\x48\xee\x58\x41\x48\x6e\xfa\x3d\xe8\x79\x03\x82\xee\xef\x91\x3b\x33\x46\x41\x83\x8a\x9e\xa2\x93\xb0\x63\x2d\x21\x79\xa7\x25\x9c\xcd\xa0\xa2\x19\xa3\x33\xce\xc9\x9d\x0b\x72\x84\x8e\x43\xb3\x3f\xb1\xbb\xf1\x0b\x9a\x19\x8a\xb1\xab\xc2\x00\xf9\x0a\x9c\xba\x8d\x30\x09\x9c\x69\x29\x58\x3b\x26\x2d\xf7\x08\xe1\x30\xfe\xa6\x1e\x1b\xc4\x23\x84\x37\x3e\x05\x3e\xf5\x3d\x1c\xaf\x1b\x73\xca\x2b\x7d\x82\xd9\x9b\x75\x19\x18\x09\x61\x84\xf0\xc1\xf1\x3f\xfe\xf6\xf7\x83\x13\x37\x8c\x35\xee\xc2\xd9\x13\xb0\xf6\x81\xb8\xe4\xda\xef\xbc\xae\xdc\xaf\xd7\x13\x58\x10\x7e\x73\x26\x3e\x43\x0e\xa9\xbe\xa2\x8e\x15\x8a\x8c\xe4\x8e\x7f\x34\x12\x7e\x51\xc3\x75\xdf\xc8\x34\x48\x9c\x2e\x85\x6d\x0a\xe5\x09\xc2\xdf\x19\x4f\x71\xad\xb1\x50\xac\xff\x19\xa4\x55\x77\x09\x3b\xbd\x22\xd4\x48\x33\x6d\x0d\x27\xd3\xf6\x51\x70\x58\xc1\x04\x1d\x46\x5d\x06\xbe\x75\xda\x57\x4e\x8f\xc3\x5f\xe6\x2e\x6f\x98\xe6\x85\x00\x21\x03\x2c\x27\x45\x76\x87\x43\xdd\x61\x0a\xb0\xe4\xb1\x24\x93\x1c\x06\xc2\x60\xb4\xf3\xe9\xf6\xec\xe9\x93\x6d\x7e\xae\x87\xb0\xaf\x57\xf6\x50\x6c\x49\xeb\xfe\x59\x82\x6c\x4a\xfd\x47\x9a\x4b\x0d\x4e\x84\x30\xc9\x32\xbf\xbd\x64\xb4\x71\x97\x53\xb3\x57\x6d\x31\xdb\x96\x4a\xea\x54\x3d\x42\xd7\x71\x06\x93\x62\xc9\x52\x13\x4a\xaa\x84\x2f\x42\xcf\x46\xa3\xb0\xbb\xb1\xe2\x5a\x00\xe1\xa9\xf2\xc3\x05\x0b\xf0\x0d\xdc\x2d\xcb\x1e\x90\x8a\xc8\x4a\x89\xd0\x49\x2f\x58\x7d\x4a\x14\x94\xba\x19\xf1\x44\x54\x27\x06\x47\xce\xe5\x50\xf7\xc1\x2d\x96\xb2\x22\x5d\x2e\xd4\x98\x55\x21\x53\xf9\x48\xd4\x73\x9d\x9c\x44\x10\xe2\x1b\xb8\x7b\x55\x64\xde\x9c\xce\x90\xfe\xf5\x2f\x49\x3d\x60\xa3\x89\x7d\x01\x32\x75\x36\x58\x5f\x4d\x5a\x2c\x85\x59\x56\xd3\x3d\x6b\xa5\x41\x0d\xf2\x8f\x7b\x22\x33\x58\xcb\x7d\x50\x5b\x49\x59\xe3\x8b\x1a\x92\x4d\xfd\x4b\xf9\x6b\x23\xc5\xba\x45\x15\xba\x46\xae\x01\x76\xf1\x7b\x1a\x92\x54\xd2\x5b\xa8\x75\xdc\xe7\x3e\x39\x18\x0f\x5c\xa9\xc6\x3e\x7b\x39\x32\xc7\x99\x59\x7c\x3f\xd9\xa9\xfb\xe0\x8f\xf1\x6e\xae\x87\xdb\xe5\xe5\xb6\x9d\x61\xcf\xd3\xa1\x7d\xbc\x9d\x2b\x71\x53\xf5\x7f\xf4\x89\x9e\xd3\x2c\x03\xf6\xd8\xbb\xb0\x64\x13\xed\xfd\xec\x7d\xa8\x81\x5b\xa5\xdc\x36\x4f\xd3\xf8\x16\xb7\x49\xe7\x74\x9d\xbb\x61\xcb\x98\xc0\xcd\xe1\xcc\xd0\x9b\xdc\xdf\xc0\x2a\x57\xf7\x37\x6d\x13\xd6\x96\x8d\x21\xb7\xce\xa1\x06\x08\x63\x52\x96\xc0\x32\xeb\xfb\x0e\xc0\x69\x0c\x7a\xc7\xf1\x81\x37\x81\xca\xa5\x6f\x0d\x0c\x35\xa2\x73\x05\x1f\xc0\x6b\x5f\x05\xc5\x79\x96\xe7\x0a\x1e\x87\x31\x2b\x64\x80\xe3\x6c\xc0\x0a\x06\x3a\x22\x71\x21\x1d\x53\xb6\x7c\xc8\x23\x45\x29\xee\xbd\x45\xf9\x3e\x78\x4b\xce\xcd\x00\xb2\x1c\xd0\x18\x1d\xc4\x92\xd3\x45\xd0\xef\xea\x6f\xd5\xc9\xee\x7d\x51\xa8\x9c\x8c\xc5\xf0\xd3\x62\x5d\xee\x28\x6b\xd7\x4d\x24\xa4\xc3\x34\x92\x9d\xfe\x96\x5d\xc2\xe9\x93\xae\x4f\xda\x3c\x79\x18\x0c\x48\x3a\xef\x6b\xb6\x3a\x2f\x3e\x0f\xf4\x21\xaa\x33\x84\xa6\x8a\xb3\x4d\xbe\xde\xd5\x55\x10\x55\xe3\x67\x1b\x48\x35\xbb\x07\x4c\x5d\xad\xdf\x6d\x83\x6a\x28\x1e\x80\xeb\xbc\x5d\xad\xb6\xa0\x7a\x93\xaa\xb2\xf1\x4a\xa9\xad\xd3\x8d\xa0\x5e\x12\x37\x42\x58\x45\x77\x6e\x58\xeb\x23\x8a\x86\xab\xf1\xfb\x1d\x16\xd7\x3b\xb9\xb5\xfe\xd4\xcf\xa9\xdc\x37\x85\x4d\xc5\xdf\x7f\x1a\x70\x3b\x31\xd3\x71\x63\x67\xd1\xbf\x6f\xa1\x5e\xbb\x79\xa7\x5c\xa7\x4c\x02\x07\x21\x29\x9b\x55\xc5\xd2\xc7\x2a\xf3\x17\x09\xba\xd0\xab\x1b\x06\xc1\xc9\xb3\x8b\xd1\xe0\xd9\xd5\xfd\xc9\xc5\x68\xf0\x6f\x57\x17\xa3\xc1\x8f\x57\xf7\x17\xa3\xe3\xab\x97\xfa\xa7\xfe\xf3\x32\xbc\x8c\xff\x7f\xe8\xc2\xe1\x6c\x41\x23\xa3\xea\x05\x19\x7c\x3d\x1b\xfc\xf7\x68\xf0\x63\xfc\xa7\xef\x0e\xfe\xfc\x2f\x4f\x8f\x86\xe3\x97\x7f\xbd\xfe\x9f\x6f\xf7\x9b\xff\x1d\x5c\x1d\xfd\x7b\x33\x7f\x15\xbc\x4c\x9a\xa7\xc1\xd5\xb7\x51\xf4\xfd\xf1\xc6\x99\x0f\x5f\x06\x2f\x93\xcb\xf8\x51\x1c\xe1\x53\x4f\x9b\xe0\x72\xf5\x34\xb9\x1c\x5e\x0e\xc3\xe0\xe2\x32\x23\x83\xaf\x97\xf1\xe0\xea\x48\xad\xec\x42\x3f\x5c\x7d\x3b\x89\xbe\xdf\x74\x56\x30\x1d\x0d\x7e\xbc\x1c\x5c\x1e\x5c\x0e\xaf\xbe\x9d\x8c\xa2\x8d\x37\xbf\x14\xc0\x75\xbd\xec\x0e\x0a\x48\x39\x48\x6f\xa8\x24\x42\xac\x82\x82\x87\x2f\x33\x6f\x3c\xe5\x90\x05\xe2\x1e\x98\xca\xd9\x7d\xd1\x44\xbf\x6f\x0f\xae\xef\x07\xf7\x71\xf8\x52\x16\x37\xc0\xea\xf9\xab\xad\xcd\xa4\x3a\x87\xb8\xa5\xb0\xba\xe6\x64\x65\x1b\x4a\x9f\xc8\xca\xa6\x0a\xf6\x73\xb5\x3e\x8e\x39\xac\xb3\xe5\xa2\xb4\x5c\xef\x61\xfd\x7a\xb9\x28\x3d\xce\xdd\x6f\x8d\xff\x40\x5f\xc9\x7c\x99\x04\x2b\xf4\x2a\xa7\xe5\xa4\x20\x3c\xfb\x8f\xcf\xc1\x61\x3c\x91\xec\x30\x6a\x5e\x26\xd9\x3e\x5c\x82\x6c\x86\x12\xcf\x40\xbe\xc9\x41\xfd\xfc\xe9\xee\x3c\x0b\x0e\xbd\x9b\x75\x18\x7a\x25\x66\x5f\x1b\xa9\x65\x98\x2d\xbd\xe8\x8e\x49\x5d\x27\x54\xc5\x53\xdc\x53\x89\x78\xf6\x6c\x79\xbb\x2e\x97\x56\x59\xbf\x94\x70\x78\xaa\x86\x77\x2f\x51\x6a\xb7\x24\x8c\xd5\x2a\x02\xbf\x1f\xd0\xda\xb7\xfd\x17\xf6\x80\x96\x5b\xd6\xb6\xcb\x1c\xfd\x3a\xef\x58\x59\x03\xdb\x5a\x98\xe4\x4b\x96\x12\xf9\x87\x3e\xad\xaa\x4e\x5d\xdf\xa7\x59\x6e\xda\x61\xbf\x98\x6a\xba\x4e\xc7\xcf\x46\x9d\x46\x53\xdd\x2d\x33\xe4\x7d\xdd\xca\x36\x8d\xf3\xa9\x98\x82\xd4\xfd\xb5\x7f\xfc\xed\xef\x4d\x67\xed\xa1\x2f\xae\xdc\x1c\xae\xb7\xbb\xea\x20\xfd\x44\x19\xe1\x77\x0e\x88\xaa\x65\x5a\x40\xc3\x8b\xcb\xf5\x68\x34\xb8\x5c\x8f\x7e\xb8\x5c\x8f\xde\x0c\x2e\xd7\xc7\x6f\xaf\x86\xfa\x73\xaa\x8a\xbc\xc6\x9b\xd3\xd9\x3c\xa7\xb3\x79\xf5\x12\xda\x0d\x4e\xee\xb9\x9a\x93\x3b\x21\x49\x7a\xe3\xf9\x81\xad\xe1\x2c\x9e\x16\xfc\x8d\x97\x62\xd9\x16\x57\x6d\x6c\x0b\x88\xc6\xf5\xcf\xba\x35\x66\x88\x23\x84\x9f\x2f\x08\xbf\x79\x71\x70\xfc\x7c\xa8\x7f\xf8\x25\x4a\xbd\x58\x0b\xd0\xf4\x8f\xdb\xe5\xd3\xc3\xdf\xbf\xe8\x9e\xf1\x99\x26\xd1\xdf\xa7\x22\xfc\x1a\x72\x90\xd0\xca\x4f\x9d\x83\x2c\x4a\xca\x18\x70\xf7\x0d\x82\x7e\x15\xf5\xeb\x52\x9a\x77\x51\x51\x57\xea\x8e\x6b\xe3\x01\x69\xf7\x8a\x9f\x67\xf4\x16\xa5\xea\xea\x8d\x0f\x49\x0e\x5c\x22\xfd\x77\x40\xd9\xb4\x38\x44\xbc\xc8\xc1\x8c\x1f\xbe\xd0\xc9\x8f\xc9\x3b\x0b\x86\xde\x51\xf9\x7e\x39\x41\xb2\x40\x02\x00\x59\x11\xa8\x98\xa2\x4c\x2f\x2b\xd3\x0d\x70\x11\x3f\x1f\x66\xf4\xf6\x05\xee\x7b\x89\xe6\x97\xb7\xdd\x0c\xbb\x4a\x74\x99\xca\x3a\xcd\x35\x74\x3b\x8f\xd5\xb7\x1b\x6e\x05\xdb\x7b\x5c\x2b\x98\x55\xc1\xab\x6f\x21\x54\x94\xf8\x2f\xfd\x10\xe0\xe1\xef\xe4\x96\x88\x94\xd3\x52\x8a\x61\x7d\x4a\xaf\x2b\xda\xf8\x77\xd1\x68\x69\x86\x0a\xd6\x78\x85\x6d\xf5\xef\x1f\xda\xc5\xeb\x58\x17\xca\xbd\x9b\xe9\xd8\x81\x59\x3b\xc4\x3b\xee\x54\xa5\x50\x5c\xdf\xc1\x07\x0e\x85\x3d\x0a\xe6\xd9\x63\x51\xc6\x7a\x5f\x79\x53\x6d\xd3\xc8\x53\xcb\x0b\xa9\xb8\xc7\x01\x47\x1e\xf1\x84\x08\x48\x10\x9e\xc3\xba\x35\xa1\x3f\x0c\x48\xd0\x0f\x2d\xf2\x3b\x09\xef\x78\xb1\x2c\x75\x49\x7a\xec\x4f\x2a\x8d\x13\xfd\x31\xa6\x3f\x4e\x44\x4a\x69\xdf\x44\x4e\x19\x7c\x58\x2e\x26\xc0\x45\xdf\xb4\x90\x77\x39\x24\xad\xd5\xb9\x5c\x3f\xc3\x54\x26\xe8\xf0\x30\xda\x4a\xf1\x49\xed\x46\x82\x0e\x93\x0e\x8d\xd0\xfb\x62\x10\xee\xb7\x4c\x5b\xf6\xee\xfc\x1c\xd6\xdb\xa4\xcf\x61\x6d\xf9\xfa\xe6\x3e\x2c\xf3\x3c\x41\x87\x71\x67\x8e\x15\xec\x23\xa7\x4c\xd7\x2b\xbd\x04\x95\x4e\x5b\xf8\x37\xce\xd3\x66\x9f\x23\xd6\x39\xfa\x5d\x37\x80\xfc\x6f\xe8\xab\x00\x54\xdd\xe3\xb0\xb5\x2d\x55\x33\xb7\x9b\xa3\xf8\x5d\xca\x4e\xf9\xe7\xb1\x3a\x39\x5b\x8b\xad\xe9\xbb\x45\xd6\xf7\x84\xad\x02\xb1\x76\x07\x65\x21\xea\x24\xc0\xb9\x6e\x9b\x5e\xaf\xfc\xcf\xe3\xdb\x57\x84\x33\xca\x66\x2d\xf7\xae\x82\x19\x12\xf4\x2b\x20\x59\x14\x28\x27\x7c\xa6\x7e\xa1\x8c\x8a\x32\x27\x77\x88\x32\x75\xd4\x63\xa4\xa3\x80\x92\xdc\xc4\x80\x7d\x5d\xbc\x5b\x58\xff\x5f\x00\x00\x00\xff\xff\x10\x35\x83\x66\x57\x33\x00\x00") +var _staticJavascriptsApplicationJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x5b\xef\x72\xdb\x38\x92\xff\x9e\xa7\xc0\x62\x3c\x67\x32\xa6\x28\xd9\x77\x99\x9d\xa1\xa3\xe4\x3c\xce\x3f\x5f\xcd\x64\x52\x49\xe6\xae\xea\x2c\xaf\x0f\x22\x5b\x12\xc6\x14\xa8\x02\x20\x4b\x4e\xac\xab\x7d\x9a\x7d\xb0\x79\x92\x2b\x80\x00\x09\x90\x94\x2c\xe7\x3e\xec\xd4\x96\x23\x02\xdd\xbf\x6e\x34\x80\xfe\x47\xee\x2d\xe1\xe8\x93\x24\x52\xa0\x21\xfa\x99\xa4\x37\xe3\x82\x41\xfc\x6b\x91\x41\x1e\xc3\x5a\x02\xcb\x82\xaf\x4f\x10\x5a\xf2\x3c\x41\xb8\x2f\x14\x21\x8e\x9e\x20\x94\xc1\x84\x2c\x73\x29\x12\xa4\xa6\x11\xc2\x0a\x63\x29\x70\x82\xcc\x7f\x98\x32\x2a\x29\xc9\xe9\x17\xca\xa6\x9a\xa5\x24\xe2\x12\xb2\x33\x69\xe8\xd8\x32\xcf\xcd\xd4\x1b\xca\xa8\x98\xd5\x73\xce\xd4\x07\x5e\x4c\x39\x88\x0a\x7c\x60\xc6\x3f\x13\x3e\x05\x59\xcb\xb4\xe3\x1f\x61\x51\x08\x2a\x0b\x4e\x41\x4f\xda\xf1\xf3\x62\x3e\xa7\x1d\xf4\x6f\x68\x0e\x8e\xe6\xce\x38\xcb\x28\x9b\xfa\x72\x37\xea\x0f\x15\x56\xdd\x04\x4d\x96\x2c\x95\xb4\x60\x41\x68\x4c\xc1\x41\x2e\x39\x43\x72\x46\x45\x3c\x05\x19\x58\xd3\x84\x68\x38\x1c\x22\x3c\x31\x9c\xf8\xd4\xa2\x65\x4b\x4e\x14\x42\x07\x16\x9d\xa0\xc0\x03\x32\xe6\x2b\xb1\x94\x8d\x2c\x65\x25\x17\x0f\x06\x89\xfe\x9f\x16\x80\xd0\x46\xff\x55\xdb\x0c\x2c\x3b\xad\x1e\x84\xc2\x42\x43\xf4\x8a\x48\x88\x17\x84\x0b\xe8\x16\x14\x9e\xfa\x8a\xd4\x4b\x0f\xc2\x5a\x36\xb0\x6c\x1b\x96\xb3\xb1\x16\x6c\x83\x20\x17\xd0\xc5\xcc\x8a\x55\x10\x36\xf5\x9e\xd3\x3c\xa7\x02\x21\x34\xd4\xa4\xbd\x52\x77\x67\x29\x90\x16\x2c\x13\x6a\xfe\x57\x22\x67\xf1\x24\x2f\x0a\x1e\x18\xae\x3e\x3a\x1e\x0c\x06\x61\x4d\xad\x8c\xa6\x64\xa1\x21\x62\xb0\xd2\x62\x03\x6d\xc8\x92\xc4\x4e\xc7\x02\xe4\xa7\x12\x38\x30\x02\x0c\x85\xb1\x73\x45\x28\x8b\x8b\x4f\xbf\x7d\x92\x9c\xb2\x69\x10\xc6\x62\x39\x16\x92\x07\xc7\xc7\x11\xfa\x31\x34\x5b\xbc\x09\x4f\x9f\xac\x28\xcb\x8a\x55\x2c\xcc\x55\x53\xa2\xf5\xb5\x3b\x7d\xf2\x44\x69\x65\xce\xda\xce\x4b\x48\xb3\x33\x29\x39\x1d\x2f\x25\x24\x08\x5f\x64\xfa\x56\x49\x10\x52\x1d\xe0\x0b\x96\xd1\x94\xc8\x82\x8b\x04\x5d\x62\x35\x8a\x23\x84\xaf\xc5\x02\x52\xf5\x63\x42\xd7\x72\xc9\x41\xfd\x9c\x17\xe9\x8d\xfa\x57\xc8\xe5\x58\x4f\x91\x1b\x3d\x9e\xc1\xbc\xd0\xe3\x64\xbe\xc8\x01\x5f\x29\x74\x31\x2b\xb8\x2c\xef\xcd\x3b\x22\x66\xfb\x9c\xf6\x9a\x1a\x57\xd6\x18\x44\xe8\xaf\x61\x75\xde\x25\xa7\xf3\x39\x64\x25\xe1\xaf\x20\x04\x99\x42\x07\xb2\xde\xfa\x72\x16\x0d\x5b\x02\x0c\x9f\x92\xb1\xc8\xa9\x0c\x70\x4f\xfd\xf7\xfa\xfd\x2b\xf4\xe1\xed\x07\xf4\xe9\xe2\xed\xfb\xb3\xcf\xbf\x7f\x7c\xad\x47\x71\x84\x4e\xc2\x78\x51\x2c\x02\x7f\x0b\x0d\x7a\xcc\x61\x91\x93\x14\x82\xfe\xdf\x46\x62\x24\x9e\xf6\x23\x84\x71\x58\x8f\xea\xc1\x83\x72\xb4\xf6\x00\x9f\x41\xc8\x8f\x90\x13\xd9\xe9\x04\x94\xf2\x0b\x22\x67\x9e\xe6\x6a\x9f\x3e\x10\xa9\x0c\x23\x8b\x5f\x8a\x15\xf0\x73\x22\xc0\x2a\x35\x29\x38\x0a\x14\x1f\x45\x43\x34\x38\x45\x14\x3d\x2f\x79\xdb\x5b\x1c\xe7\xc0\xa6\x72\x76\x8a\xe8\xd1\x51\x7d\x09\xd5\x1d\x55\x32\x63\xca\x32\x58\xff\x36\x09\xb6\x70\x5f\xd2\xab\x10\xbd\x40\xbd\xe3\x9a\xb5\xde\x47\xbe\x84\x53\x33\xb8\x71\xee\xa1\x99\x9e\x90\x5c\x40\xb5\x91\x13\x9a\xc3\x79\xc1\x24\x30\x29\x7e\x57\x11\x62\xdb\xe9\xb8\xc4\xfd\x89\x76\xb2\x91\x63\x8d\xca\x4d\xdf\xfd\xb6\x62\xc0\x71\xd8\x3d\xf9\x9e\xcc\xc1\x9f\x73\x4f\x58\xd4\x69\xde\xab\xf8\x8f\x82\xb2\x00\xf7\x71\xd8\xa9\xac\xa3\x69\x4a\xf2\x7c\x4c\xd2\x9b\x08\x01\xe7\x05\xb7\x8a\x1f\xc4\xe4\x0f\xb2\x0e\xac\x7d\x74\xfc\xd3\x92\x1a\x6b\x0e\xc2\xc8\x90\x88\x65\x9a\x82\x10\x09\xaa\x10\xad\x7b\x53\xb8\x49\xf9\x4f\x69\x51\xd7\x2f\xb8\xb7\xdf\x8b\xc1\xe7\x45\x9e\x83\xd6\xb1\x23\x10\x4f\x6c\x68\x52\x42\xe6\xca\x51\x24\x16\xc4\xc0\x1a\x7f\x33\xa9\x91\x95\xcb\xb1\x82\x02\x2b\x59\xfb\xa0\xff\xa4\xb0\x72\x45\xab\x67\xdf\xf1\x24\xca\x5d\x10\x29\xae\xd3\x82\x49\x42\xd5\x76\x39\x92\xf5\x94\x7a\x5e\x14\x79\x4e\xd9\xf4\x33\x4d\x6f\x80\x27\x55\x0c\xb7\x01\xae\x39\x6e\xc8\x2f\x98\x04\x7e\x4b\xf2\x04\x3d\x1b\xe8\x18\x5b\xa5\x0e\x5d\x6e\x41\xef\x42\x4e\x85\x04\xf6\xb9\x28\x8f\xb8\x56\x23\x42\x38\x9d\x11\x36\x05\x7b\xc8\x38\xb0\x0c\x78\x58\x33\xe9\xb0\xf1\xca\xd3\xc5\xde\xbd\x7a\xfe\x43\xa9\x53\x50\x1f\x9c\x12\x67\x57\x70\xd6\xf2\xb7\x44\x46\x83\x5c\x2c\x3c\x60\x6f\xa6\x5b\xa5\x4d\x97\x8c\x19\x11\xe7\x7a\x91\x59\x50\xa7\x45\x4d\x69\xcb\x45\x46\x24\xd8\xe9\xbd\xf1\xaa\x74\xa7\x1b\xcf\x3d\x3a\x7b\xe2\xa9\x1b\xbf\x0d\x2c\x87\xfd\x91\x6c\xe2\xd6\x8d\x65\x66\xf7\x46\xf3\xd2\xc3\x6e\x48\x97\x64\x6f\x5c\x9b\x8e\x76\x43\x9a\x59\x17\x4d\x9f\x2e\xf7\xd0\x6d\x3b\xed\xde\xb5\x42\x43\x24\x40\xda\x3b\x13\xb4\x38\x50\x79\x1d\xf5\x1d\x2e\x95\x9c\x80\x4c\x67\x95\xe0\xc8\xc3\xb4\x38\xf5\x71\x77\xce\xea\xae\x33\xef\xeb\xf4\x97\x56\x36\x9a\xe6\x40\x78\xa5\x65\x9b\xa5\xd3\x0e\xaf\x1a\x8e\xa2\xdb\x1c\x3e\xd5\x63\xec\x51\x6e\x85\xe5\x0f\x42\x6b\x91\x2a\x45\xac\x2c\xb0\x9f\x26\x4d\xbc\x46\xae\xec\xfb\xbd\xfd\x8c\xe4\xf3\x34\xad\xe4\x0b\xec\x50\xeb\x20\xc0\xdf\xa5\x84\x67\xd7\x16\xe7\xfa\x96\xe4\x4b\x95\x26\x49\x58\x4b\xf7\xe8\x66\x95\xd6\xf5\xca\x7d\xcf\xb1\x25\xad\x11\xba\x9c\xb1\x89\x4d\xf9\xf4\xb9\x78\xb7\x9c\x93\xca\x02\x07\x01\x96\x54\xe6\x95\x58\xfc\x96\x4a\x5e\x8c\x13\x84\xd1\x91\xe1\xaf\x29\xbf\x5b\x18\x79\xd7\x63\xc2\x2d\x87\x21\x8a\x53\x21\x02\xbc\xa2\x99\x9c\x59\xb7\x5e\x6a\xaf\x03\x7e\xed\x01\xd1\x11\xc2\xdf\xe3\xa6\xfd\x77\xf9\xe5\x0e\xc1\x1c\xe6\xc5\x2d\x9c\xe7\x44\xc9\xb4\x73\xbd\x31\xe1\x3d\xc2\xe8\x5c\x25\x7a\xc8\x1b\x15\x92\xd3\x05\x64\xb8\xa1\x25\x3e\x1e\x0c\x2a\x5d\x1a\x3b\x67\x9d\xe8\xae\x9d\xb3\x21\xbb\xda\xb9\x19\xcd\x20\x68\x6f\xa0\xad\xae\x8c\xd3\xd6\x29\x65\x4a\x72\xb0\xa5\x48\x18\x4f\x48\x06\x17\x2c\xc0\x13\x22\x24\x6e\xee\xb2\x76\xc1\xbb\xf5\xc8\x61\x5f\x25\xb4\xa7\x7f\xac\x06\xc6\x71\xef\xd2\x21\x2d\x49\xf6\xd2\xa2\x8a\x12\x8f\xd5\xc3\xf5\xf6\xbb\x94\xe1\x0e\xdd\x5e\x1a\xf9\x91\xe6\xb1\x6a\x99\x88\xb1\x4b\x23\x59\x92\xec\xa5\x4c\x15\x9e\xf6\xd7\xc3\xbb\xdb\x3b\xbd\x41\x79\xd8\xc5\x8a\xaa\x48\xd3\x94\x6c\xfb\x1f\x96\x2d\x25\x02\x1a\xfd\xa1\xc4\x71\xd5\xda\xb7\xe0\x0b\x77\xda\xa6\x4c\x63\x0e\xe4\xe6\xd4\x01\x99\x12\x39\x03\xde\x8d\xf0\xd6\xce\x21\x77\xe3\xb6\x63\x11\x46\xf2\xbb\x2d\xda\x9c\xd9\x39\x1f\x6b\x1b\x54\xd5\xe3\x69\x23\xbd\x71\xdb\x3f\x0d\x66\xd3\x54\x6b\x33\xfd\xce\x6e\x58\xb1\x62\x5d\x3c\x5e\x79\x66\x38\x8e\x10\x46\x81\x72\xb5\xba\x17\x73\xc1\xda\x87\xc1\xcd\x1d\x95\xeb\x0c\xcb\x6e\x54\xab\x53\x61\x2a\x83\xaa\x5b\xa1\x9e\x83\xaf\x2a\xe7\x57\x67\xb0\x59\x12\x84\xcd\x82\xe6\xa1\xc2\x42\x92\xa9\x2a\xef\x12\x84\x65\x59\x50\xc0\x6d\x59\x9e\x99\xa6\x62\x9a\xd3\xf4\x06\xc9\x2c\x4e\x8b\xbc\xa7\xcb\x69\x82\x55\x29\x32\x2b\x56\x46\x02\xae\x7a\x72\x12\xe6\x0b\x55\x8d\x27\xe8\x3a\xb6\xbf\x03\xa5\xa5\x7d\xb0\x8e\x55\xdd\x13\x39\xcf\x83\x30\xdc\x99\xdd\x6b\x93\x1d\xa8\x1c\x4f\x11\x9b\x52\xda\xc0\x3a\xe6\x24\xb6\x25\x23\xc2\x30\xce\x88\x24\x01\xb6\x72\xdc\x80\xb5\x2d\x34\x39\x5d\x84\x56\xd5\xa0\x84\x93\x2c\x33\x01\x49\xd5\xf1\x3d\x5e\x92\xe2\xb0\x63\xf3\x15\x4f\x5d\xed\x16\x7c\x4e\xa4\x84\xcc\x16\xc5\xdb\xae\xef\x22\xa7\x52\x20\xdd\x5c\xeb\x72\xeb\xa6\x5d\x61\x7a\x2c\x7d\xec\x34\xd2\x54\x7c\x60\x64\x0e\x8a\xb5\x84\x71\x5b\x2c\x8a\x22\xa3\x1c\x52\x55\xc3\x5b\x70\xc8\x73\xba\x10\x54\xd0\x2f\x10\x18\x96\xaa\x50\x8f\xd0\x0f\x83\x08\x9d\x3c\x73\x2c\xe5\xf0\x0f\x87\x08\xe3\x76\xaf\xf3\xb9\x90\xbc\x60\xd3\x17\xea\xb0\x5f\xc7\x20\x52\xb2\x80\xc0\x2a\xa6\x8f\xf6\xf3\xbe\x25\xe9\x30\x59\xc5\x52\x49\xd2\x3c\x7d\xac\x39\x1f\x89\xad\xed\xee\xac\xd0\xb1\xb8\x90\x3c\x42\x73\xca\x7e\xd1\x6d\x9b\x08\x41\x36\x85\xf2\xb7\x5d\x92\x90\x2a\x91\x35\x1e\x59\x48\xee\x58\x41\x48\x6e\xfa\x3d\xe8\x79\x0d\x82\xee\xef\x91\x3b\x33\x44\x41\x8d\x8a\x9e\xa2\x93\xb0\x65\x2d\x21\x79\xab\x25\x9c\x4d\xa1\xa4\x19\xa2\x33\xce\xc9\x9d\x0b\x72\x84\x8e\x43\xb3\x3f\xb1\xbb\xf1\x73\x9a\x19\x8a\xa1\xab\x42\x0f\xf9\x0a\x9c\xba\x8d\x30\x09\x9c\x69\x29\x58\x3b\x26\x2d\xf7\x08\xe1\x30\xfe\xaa\x1e\x6b\xc4\x23\x84\x37\x3e\x05\x3e\xf5\x3d\x1c\xaf\x1a\x73\xca\x2b\x7d\x84\xe9\xeb\xf5\x22\x30\x12\xc2\x08\xe1\x83\xe3\x3f\xff\xfe\x8f\x83\x13\x37\x8c\xd5\xee\xc2\xd9\x13\xb0\xf6\x81\x78\xc1\xb5\xdf\x79\x55\xba\x5f\xaf\x27\x30\x27\xfc\xe6\x4c\x7c\x82\x1c\x52\x7d\x45\x1d\x2b\x14\x19\xc9\x1d\xff\x68\x24\xfc\xaa\x86\xab\xbe\x91\x69\x90\x38\x5d\x0a\xdb\x14\xca\x13\x84\xbf\x33\x9e\xe2\x5a\x63\xa1\x58\xff\xd3\x4b\xcb\xee\x12\x76\x7a\x45\xa8\x96\x66\xda\x1a\x4e\xa6\xed\xa3\xe0\xb0\x84\x09\x5a\x8c\xba\x0c\x7c\xe3\xb4\xaf\x9c\x1e\x87\xbf\xcc\x5d\xde\x30\xcd\x0b\x01\x42\x06\x58\x8e\x8b\xec\x0e\x87\xba\xc3\x14\x60\xc9\x63\x49\xc6\x39\xf4\x84\xc1\x68\xe6\xd3\xcd\xd9\xd3\x27\xdb\xfc\x5c\x07\x61\x57\xaf\xec\xa1\xd8\x92\x56\xfd\xb3\x04\xd9\x94\xfa\x5b\x9a\x4b\x35\x4e\x84\x30\xc9\x32\xbf\xbd\x64\xb4\x71\x97\x53\xb1\x97\x6d\x31\xdb\x96\x4a\xaa\x54\x3d\x42\xd7\x71\x06\xe3\x62\xc9\x52\x13\x4a\xca\x84\x2f\x42\xcf\x06\x83\xb0\xbd\xb1\xe2\x5a\x00\xe1\xa9\xf2\xc3\x05\x0b\xf0\x0d\xdc\x2d\x17\x1d\x20\x25\x91\x95\x12\xa1\x93\x4e\xb0\xea\x94\x28\x28\x75\x33\xe2\xb1\x28\x4f\x0c\x8e\x9c\xcb\xa1\xee\x83\x5b\x2c\x65\x45\xba\x9c\xab\x31\xab\x42\xa6\xf2\x91\xa8\xe3\x3a\x39\x89\x20\xc4\x37\x70\x77\x5e\x64\xde\x9c\xce\x90\xfe\xf5\xaf\x49\x35\x60\xa3\x89\x7d\x01\x32\x71\x36\x58\x5f\x4d\x5a\x2c\x85\x59\x56\xdd\x3d\x6b\xa4\x41\x35\xf2\x4f\x7b\x22\x33\x58\xcb\x7d\x50\x1b\x49\x59\xed\x8b\x6a\x92\x4d\xf5\x4b\xf9\x6b\x23\xc5\xba\x45\x15\xba\x06\xae\x01\x76\xf1\x7b\x1a\x92\x54\xd2\x5b\xa8\x74\xdc\xe7\x3e\x39\x18\x0f\x5c\xa9\xda\x3e\x7b\x39\x32\xc7\x99\x59\x7c\x3f\xd9\xa9\xfa\xe0\x8f\xf1\x6e\xae\x87\xdb\xe5\xe5\xb6\x9d\x61\xcf\xd3\xa1\x7d\xbc\x9d\x2b\x71\x53\xf6\x7f\xf4\x89\x9e\xd1\x2c\x03\xf6\xd8\xbb\xb0\x64\x63\xed\xfd\xec\x7d\xa8\x80\x1b\xa5\xdc\x36\x4f\x53\xfb\x16\xb7\x49\xe7\x74\x9d\xdb\x61\xcb\x98\xc0\xcd\xe1\xcc\xd0\xeb\xdc\xdf\xc0\x32\x57\xf7\x37\x6d\x13\x56\x96\x8d\x21\xb7\xce\xa1\x02\x08\x63\xb2\x58\x00\xcb\xac\xef\x3b\x00\xa7\x31\xe8\x1d\xc7\x07\xde\x04\x2a\x97\xbe\x35\x30\x54\x88\xce\x15\x7c\x00\xaf\x79\x15\x14\xe7\x59\x9e\x2b\x78\x1c\xc6\xac\x90\x01\x8e\xb3\x1e\x2b\x18\xe8\x88\xc4\x85\x74\x4c\xd9\xf0\x21\x8f\x14\xa5\xb8\xf7\x16\xe5\xfb\xe0\x2d\x39\x37\x03\xc8\x72\x40\x43\x74\x10\x4b\x4e\xe7\x41\xb7\xab\xbf\x55\x27\xbb\xf3\x45\xa1\x72\x32\x16\xc3\x4f\x8b\x75\xb9\xa3\xac\x5d\x35\x91\x90\x0e\xd3\x48\xb6\xfa\x5b\x76\x09\xa7\x4f\xda\x3e\x69\xf3\xe4\x61\x30\x20\xe9\xac\xab\xd9\xea\xbc\xf8\x3c\xd0\x87\xa8\xca\x10\xea\x2a\xce\x36\xf9\x3a\x57\x57\x42\x94\x8d\x9f\x6d\x20\xe5\xec\x1e\x30\x55\xb5\x7e\xb7\x0d\xaa\xa6\x78\x00\xae\xf5\x76\xb5\xdc\x82\xf2\x4d\xaa\xca\xc6\x4b\xa5\xb6\x4e\xd7\x82\x3a\x49\xdc\x08\x61\x15\xdd\xb9\x61\x8d\x8f\x28\x6a\xae\xda\xef\xb7\x58\x5c\xef\xe4\xd6\xfa\x13\x3f\xa7\x72\xdf\x14\xd6\x15\x7f\xf7\x69\xc0\xcd\xc4\x4c\xc7\x8d\x9d\x45\xff\xbe\x85\x7a\xe5\xe6\x9d\x72\x9d\x32\x09\x1c\x84\xa4\x6c\x5a\x16\x4b\x1f\xca\xcc\x5f\x24\xe8\x52\xaf\xae\x1f\x04\x27\xcf\x2e\x07\xbd\x67\x57\xf7\x27\x97\x83\xde\xbf\x5d\x5d\x0e\x7a\x3f\x5d\xdd\x5f\x0e\x8e\xaf\x5e\xea\x9f\xfa\xcf\xcb\x70\x14\xff\x73\xe8\xc2\xfe\x74\x4e\x23\xa3\xea\x25\xe9\x7d\x39\xeb\xfd\xf7\xa0\xf7\x53\xfc\x97\xef\x0e\xbe\xff\x97\xa7\x47\xfd\xe1\xcb\xbf\x5d\xff\xcf\xd7\xfb\xcd\xff\xf6\xae\x8e\xfe\xbd\x9e\xbf\x0a\x5e\x26\xf5\x53\xef\xea\xeb\x20\xfa\xe1\x78\xe3\xcc\x87\x2f\x83\x97\xc9\x28\x7e\x14\x47\xf8\xd4\xd3\x26\x18\xad\x9e\x26\xa3\xfe\xa8\x1f\x06\x97\xa3\x8c\xf4\xbe\x8c\xe2\xde\xd5\x91\x5a\xd9\xa5\x7e\xb8\xfa\x7a\x12\xfd\xb0\x69\xad\x60\x32\xe8\xfd\x34\xea\x8d\x0e\x46\xfd\xab\xaf\x27\x83\x68\xe3\xcd\x2f\x05\x70\x5d\x2f\xbb\x83\x02\x52\x0e\xd2\x1b\x5a\x10\x21\x56\x41\xc1\xc3\x97\x99\x37\x9e\x72\xc8\x02\x71\x0f\x4c\xe5\xec\xbe\x68\xa2\xdf\xb7\x07\xd7\xf7\xbd\xfb\x38\x7c\x29\x8b\x1b\x60\xd5\xfc\xd5\xd6\x66\x52\x95\x43\xdc\x52\x58\x5d\x73\xb2\xb2\x0d\xa5\x8f\x64\x65\x53\x05\xfb\xb9\x5a\x17\xc7\x0c\xd6\xd9\x72\xbe\xb0\x5c\xef\x60\xfd\x6a\x39\x5f\x78\x9c\xbb\xdf\x1a\x7f\x43\x5f\xc9\x7c\x99\x04\x2b\x74\x9e\xd3\xc5\xb8\x20\x3c\xfb\x8f\x4f\xc1\x61\x3c\x96\xec\x30\xaa\x5f\x26\xd9\x3e\x5c\x82\x6c\x86\x12\x4f\x41\xbe\xce\x41\xfd\xfc\xf9\xee\x22\x0b\x0e\xbd\x9b\x75\x18\x7a\x25\x66\x57\x1b\xa9\x61\x98\x2d\xbd\xe8\x96\x49\x5d\x27\x54\xc6\x53\xdc\x51\x89\x78\xf6\x6c\x78\xbb\x36\x97\x56\x59\xbf\x94\x70\x78\xca\x86\x77\x27\x51\x6a\xb7\x24\x8c\xd5\x2a\x02\xbf\x1f\xd0\xd8\xb7\xfd\x17\xf6\x80\x96\x5b\xd6\xb6\xcb\x1c\xdd\x3a\xef\x58\x59\x0d\xdb\x58\xd8\x14\xe4\xbb\x42\xc8\xb2\xa5\xfa\xd0\xe7\x0a\xce\x3b\x93\xdf\xb9\xf2\xb2\x36\x2a\xe1\x29\x95\xb3\xe5\x18\x87\xfa\x85\xa5\x8a\x4c\xb6\xdb\xf6\xb6\x9c\xf0\x8e\x8b\x1a\xfc\x85\x8c\xb1\xf3\x75\xd7\x92\xa5\x44\x7e\xd3\xf7\x5d\xa5\x66\x5d\xdf\x87\xb9\xb9\x8f\xfd\x6c\xab\x6e\x7d\x1d\x3f\x1b\xb4\xba\x5d\x55\xcb\xce\x90\x77\xb5\x4c\x9b\x34\xce\xf7\x6a\x0a\x52\x37\xf9\xfe\xfc\xfb\x3f\xea\xc5\x3d\xf4\xd9\x97\x9b\x48\x76\xb6\x78\x1d\xa4\x9f\x29\x23\xfc\xce\x01\x51\x05\x55\x03\xa8\x7f\x39\x5a\x0f\x06\xbd\xd1\x7a\xf0\xe3\x68\x3d\x78\xdd\x1b\xad\x8f\xdf\x5c\xf5\xf5\x37\x5d\x25\x79\x85\x37\xa3\xd3\x59\x4e\xa7\xb3\xf2\x4d\xb8\x1b\x21\xdd\xc3\x3d\x23\x77\x42\x92\xf4\xc6\x73\x46\x5b\x63\x6a\x3c\x29\xf8\x6b\x2f\xcf\xb3\x7d\xb6\xca\xd8\x16\x10\x0d\xab\x9f\x55\x7f\xce\x10\x47\x08\x3f\x9f\x13\x7e\xf3\xe2\xe0\xf8\x79\x5f\xff\xf0\xeb\xa4\x6a\xb1\x16\xa0\x6e\x62\x37\x6b\xb8\x3d\x4f\xf5\x99\x26\xd1\x1f\xc9\x22\xfc\x0a\x72\x90\x80\xfd\x0c\xd5\x5c\x34\x34\x44\xf8\x79\x46\x6f\x51\xaa\x2e\xe7\xf0\x90\xe4\xc0\x25\xd2\x7f\x7b\x94\x4d\x8a\x43\xc4\x8b\x1c\xcc\xf8\xe1\x0b\x9d\x1e\x99\xcc\xb4\x60\xe8\x7b\x81\x64\x81\x04\x80\x85\x13\xa8\x98\xa0\x4c\xcb\xcb\x74\x7b\x5c\xc4\xcf\xfb\x19\xbd\x7d\x81\xdd\x9c\x74\x56\x08\xe9\x7c\x16\x68\x6f\xac\x9f\xb8\x96\x6f\xe4\xde\x2c\x59\x8a\x86\xed\x45\xef\x70\x1d\xee\xeb\x98\x32\xc4\x98\x99\x6a\x5b\xf0\xf7\x02\x47\x5a\x8b\xce\x37\x7f\xc8\x69\x48\x34\x64\x88\x05\x65\x0c\xb8\x27\x42\x01\xfc\xb6\x94\x06\x21\x72\x14\x0f\xc2\x1d\xc5\x45\xb5\x09\x6b\x6b\x0b\xb7\xe9\x5a\x7e\xb6\xe2\x16\xef\x9d\x97\xa4\x84\x59\x15\xbc\xfc\x0c\x44\x05\xc8\xff\xd2\x0f\x01\xee\xff\x41\x6e\x89\x48\x39\x5d\x48\xd1\xaf\xee\xc6\x75\x49\x1b\xff\x21\xea\xb5\x9a\xa1\x82\xd5\xbe\x68\x5b\xe9\xff\x4d\x06\xb9\x8e\x75\x8f\xa0\x73\x0f\x9b\x87\x51\xaf\x7e\xc7\x4d\x2e\x15\x8a\xab\x9b\xff\xc0\x59\x68\x9c\x00\x8f\x45\x19\xeb\x5d\x19\x48\xb4\x4d\x23\x4f\x2d\x2f\x9b\xc0\x1d\xb1\x27\xf2\x88\xc7\x44\x40\x82\xf0\x0c\xd6\x8d\x09\xfd\x4d\x44\x82\x7e\x6c\x90\xdf\x49\x78\xcb\x8b\xe5\x42\x57\xe3\xc7\xfe\xa4\xd2\x38\xd1\xdf\xa1\xfa\xe3\x44\xa4\x94\x76\x4d\xe4\x94\xc1\xfb\xe5\x7c\x0c\x5c\x74\x4d\x0b\x79\x97\x43\xd2\x58\x9d\xcb\xf5\x0b\x4c\x64\x82\x0e\x0f\xa3\xad\x14\x1f\xd5\x6e\x24\xe8\x30\x69\xd1\x08\xbd\x2f\x06\xe1\x7e\xcb\xb4\x65\x6f\xcf\xcf\x60\xbd\x4d\xfa\x0c\xd6\x96\xaf\x6b\xee\xfd\x32\xcf\x13\x74\x18\xb7\xe6\x58\xc1\x3e\x70\xca\x74\xa9\xd6\x49\x50\xea\xb4\x85\x7f\xe3\x3c\x6d\xf6\x39\x62\xad\xa3\xdf\x76\x26\xc8\xff\xbf\x0f\x94\x61\xaf\xbc\xc7\x61\x63\x5b\xca\x3e\x76\x3b\x3d\xf3\x1b\xb4\xad\xca\xd7\x63\x75\xd2\xd5\x06\x5b\xdd\x72\x8c\xac\xef\x09\x5b\x2e\xcf\xb8\x83\x45\x21\xaa\xd4\xc3\xb9\x6e\x9b\xa8\xcb\x19\x7f\x8b\x9b\xfc\x7f\xb9\xf4\xad\x41\x6b\x45\x38\xa3\x6c\xda\x88\x5b\x2a\x84\x22\x41\xbf\x00\x92\x45\x81\x72\xc2\xa7\xea\x17\xca\xa8\x58\xe4\xe4\x0e\x51\xa6\x8e\x7a\x8c\x74\x78\x53\x92\x55\x70\x7b\x4b\xe5\xbb\xe5\xd8\xc6\xaf\xad\x81\xc2\xef\x78\xea\x9e\xc2\xff\x05\x00\x00\xff\xff\x68\xc3\x30\x59\x52\x34\x00\x00") func staticJavascriptsApplicationJsBytes() ([]byte, error) { return bindataRead( @@ -329,7 +296,7 @@ func staticJavascriptsApplicationJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/javascripts/application.js", size: 13143, mode: os.FileMode(420), modTime: time.Unix(1528532866, 0)} + info := bindataFileInfo{name: "static/javascripts/application.js", size: 13394, mode: os.FileMode(420), modTime: time.Unix(1583789200, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -349,7 +316,7 @@ func staticJavascriptsBackboneJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/javascripts/backbone.js", size: 23355, mode: os.FileMode(420), modTime: time.Unix(1528458857, 0)} + info := bindataFileInfo{name: "static/javascripts/backbone.js", size: 23355, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -369,7 +336,7 @@ func staticJavascriptsBootstrapJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/javascripts/bootstrap.js", size: 48944, mode: os.FileMode(420), modTime: time.Unix(1520701936, 0)} + info := bindataFileInfo{name: "static/javascripts/bootstrap.js", size: 48944, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -389,7 +356,7 @@ func staticJavascriptsClipboardJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/javascripts/clipboard.js", size: 10662, mode: os.FileMode(420), modTime: time.Unix(1528532866, 0)} + info := bindataFileInfo{name: "static/javascripts/clipboard.js", size: 10662, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -409,7 +376,7 @@ func staticJavascriptsHexdumpJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/javascripts/hexdump.js", size: 5036, mode: os.FileMode(420), modTime: time.Unix(1528532866, 0)} + info := bindataFileInfo{name: "static/javascripts/hexdump.js", size: 5036, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -429,7 +396,7 @@ func staticJavascriptsHighlightJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/javascripts/highlight.js", size: 65069, mode: os.FileMode(384), modTime: time.Unix(1520769326, 0)} + info := bindataFileInfo{name: "static/javascripts/highlight.js", size: 65069, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -449,7 +416,7 @@ func staticJavascriptsHighlight_workerJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/javascripts/highlight_worker.js", size: 159, mode: os.FileMode(420), modTime: time.Unix(1520799258, 0)} + info := bindataFileInfo{name: "static/javascripts/highlight_worker.js", size: 159, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -469,7 +436,7 @@ func staticJavascriptsJquery331Js() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/javascripts/jquery-3.3.1.js", size: 86927, mode: os.FileMode(420), modTime: time.Unix(1520701936, 0)} + info := bindataFileInfo{name: "static/javascripts/jquery-3.3.1.js", size: 86927, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -489,7 +456,7 @@ func staticJavascriptsPopperJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/javascripts/popper.js", size: 19188, mode: os.FileMode(420), modTime: time.Unix(1520701936, 0)} + info := bindataFileInfo{name: "static/javascripts/popper.js", size: 19188, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -509,7 +476,7 @@ func staticJavascriptsUnderscoreJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/javascripts/underscore.js", size: 16449, mode: os.FileMode(420), modTime: time.Unix(1528458857, 0)} + info := bindataFileInfo{name: "static/javascripts/underscore.js", size: 16449, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -529,7 +496,7 @@ func staticStylesheetsApplicationCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/stylesheets/application.css", size: 1734, mode: os.FileMode(420), modTime: time.Unix(1528532866, 0)} + info := bindataFileInfo{name: "static/stylesheets/application.css", size: 1734, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -549,7 +516,7 @@ func staticStylesheetsBootstrapCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/stylesheets/bootstrap.css", size: 153165, mode: os.FileMode(420), modTime: time.Unix(1520701936, 0)} + info := bindataFileInfo{name: "static/stylesheets/bootstrap.css", size: 153165, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -569,7 +536,7 @@ func staticStylesheetsHighlightCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/stylesheets/highlight.css", size: 1149, mode: os.FileMode(420), modTime: time.Unix(1520798400, 0)} + info := bindataFileInfo{name: "static/stylesheets/highlight.css", size: 1149, mode: os.FileMode(420), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -589,7 +556,7 @@ func staticStylesheetsOpeniconicCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/stylesheets/openiconic.css", size: 9395, mode: os.FileMode(493), modTime: time.Unix(1528532866, 0)} + info := bindataFileInfo{name: "static/stylesheets/openiconic.css", size: 9395, mode: os.FileMode(493), modTime: time.Unix(1582905682, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -646,7 +613,6 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "static/.DS_Store": staticDs_store, "static/fonts/open-iconic.eot": staticFontsOpenIconicEot, "static/fonts/open-iconic.otf": staticFontsOpenIconicOtf, "static/fonts/open-iconic.svg": staticFontsOpenIconicSvg, @@ -656,7 +622,6 @@ var _bindata = map[string]func() (*asset, error){ "static/images/gopher_head.png": staticImagesGopher_headPng, "static/images/spinner.gif": staticImagesSpinnerGif, "static/index.html": staticIndexHtml, - "static/javascripts/.DS_Store": staticJavascriptsDs_store, "static/javascripts/application.js": staticJavascriptsApplicationJs, "static/javascripts/backbone.js": staticJavascriptsBackboneJs, "static/javascripts/bootstrap.js": staticJavascriptsBootstrapJs, @@ -715,7 +680,6 @@ type bintree struct { var _bintree = &bintree{nil, map[string]*bintree{ "static": &bintree{nil, map[string]*bintree{ - ".DS_Store": &bintree{staticDs_store, map[string]*bintree{}}, "fonts": &bintree{nil, map[string]*bintree{ "open-iconic.eot": &bintree{staticFontsOpenIconicEot, map[string]*bintree{}}, "open-iconic.otf": &bintree{staticFontsOpenIconicOtf, map[string]*bintree{}}, @@ -730,7 +694,6 @@ var _bintree = &bintree{nil, map[string]*bintree{ }}, "index.html": &bintree{staticIndexHtml, map[string]*bintree{}}, "javascripts": &bintree{nil, map[string]*bintree{ - ".DS_Store": &bintree{staticJavascriptsDs_store, map[string]*bintree{}}, "application.js": &bintree{staticJavascriptsApplicationJs, map[string]*bintree{}}, "backbone.js": &bintree{staticJavascriptsBackboneJs, map[string]*bintree{}}, "bootstrap.js": &bintree{staticJavascriptsBootstrapJs, map[string]*bintree{}}, diff --git a/static/index.html b/static/index.html index ca9457bd..612cd4af 100644 --- a/static/index.html +++ b/static/index.html @@ -180,8 +180,8 @@ diff --git a/static/javascripts/application.js b/static/javascripts/application.js index 8d7afdde..507a0012 100644 --- a/static/javascripts/application.js +++ b/static/javascripts/application.js @@ -335,6 +335,10 @@ var FindingModal = Backbone.View.extend({ $("#modal_file_contents").hide(); $("#modal_file_hexdump").show(); }, + getHostName: function() { + if (this.model.get("CommitUrl").indexOf("github") !== -1) return "Github"; + return "GitLab"; + }, truncatedCommitMessage: function() { var message = this.model.trimmedCommitMessage(); if (message.length <= 150) { @@ -356,9 +360,12 @@ var FindingModal = Backbone.View.extend({ }, fetchFileContents: function() { if (this.model.get("Action") == "Delete") { - $("#modal_file_spinner_container").fadeOut("fast", function() { - $("#modal_file_contents_container").html("").fadeIn("fast"); - }); + var content = ""; + var host = this.getHostName(); + var fadeInFunc = function() { + $("#modal_file_contents_container").html(content.replace("%s", host)).fadeIn("fast"); + } + $("#modal_file_spinner_container").fadeOut("fast", fadeInFunc()); return; } var context = this; From 3b9dd912fbed57278ae9b1cbd00f93764be1f207 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 9 Mar 2020 17:57:56 -0400 Subject: [PATCH 049/226] disable buttons on the modal when a delete action is shown --- core/bindata.go | 8 ++++---- static/index.html | 4 ++-- static/javascripts/application.js | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/bindata.go b/core/bindata.go index 9a1af005..d72fa623 100644 --- a/core/bindata.go +++ b/core/bindata.go @@ -261,7 +261,7 @@ func staticImagesSpinnerGif() (*asset, error) { return a, nil } -var _staticIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x5a\x6d\x6f\xdc\xb8\x11\xfe\x7e\xbf\x82\xc7\x62\x0f\x09\x70\x5a\x39\x35\x0e\x28\x1c\x69\xd1\x34\xce\x35\x06\x2e\xc9\x21\x71\x0b\xf4\xd3\x82\x12\x67\x25\xc6\x14\xa9\x92\xa3\x5d\xbb\x45\xff\x7b\x41\x52\xd2\xea\x6d\x73\x59\x27\x01\x02\x04\x1b\x91\x9c\x37\x3e\x1c\x0e\x67\x48\x27\x3f\x72\x9d\xe3\x43\x0d\xa4\xc4\x4a\x6e\x7e\x48\xdc\x7f\x44\x32\x55\xa4\x14\x14\xdd\xfc\x40\x48\x52\x02\xe3\xee\x83\x90\xa4\x02\x64\x24\x2f\x99\xb1\x80\x29\x6d\x70\x17\xfd\x85\x0e\x87\x14\xab\x20\xa5\x7b\x01\x87\x5a\x1b\xa4\x24\xd7\x0a\x41\x61\x4a\x0f\x82\x63\x99\x72\xd8\x8b\x1c\x22\xdf\xf8\x99\x08\x25\x50\x30\x19\xd9\x9c\x49\x48\x9f\xfd\x4c\x6c\x69\x84\xba\x8b\x50\x47\x3b\x81\xa9\xd2\x0b\xa2\x39\xd8\xdc\x88\x1a\x85\x56\x03\xe9\x7f\x17\x68\x74\x76\x45\x7e\x6f\x10\x85\x2a\x08\x96\x40\xde\xd5\xa0\xc8\x07\xdd\x98\x1c\x88\x50\xe4\xdd\x87\x9b\xb7\xb7\x0b\x02\x59\x83\xa5\x36\x03\x59\x6f\x44\x5e\x32\x90\xe4\x35\x28\x23\xee\x2c\x28\xf2\xe4\xaf\x95\xc8\xcb\xae\xf9\x94\x6e\x7e\x08\x52\x50\xa0\x84\x4d\xd0\x9d\xc4\xa1\xd5\x0e\x49\xa1\xee\x48\x69\x60\x97\xd2\xd8\xe2\x83\x04\x5b\x02\xa0\x8d\x33\xad\xd1\xa2\x61\xf5\x3a\xb7\x96\x12\x03\x32\xa5\xc7\xf1\xce\xbc\x53\xdc\xba\x06\x25\x72\xad\x44\xfe\x28\xf6\x52\x14\xa5\x14\x45\x89\x8f\xe2\x66\x75\x2d\x45\xce\x1c\xf2\xa7\xf9\x93\x38\x38\x8b\xfb\xcc\x34\x7f\xe8\xf0\x50\x6c\x4f\x72\xc9\xac\x4d\xa9\x62\xfb\x8c\x19\x12\xfe\x8b\xe0\xbe\x66\x8a\x47\x15\xef\x3a\xbc\x81\x24\x2b\xc2\x47\x6b\x14\x21\x09\x17\xbd\x04\xb7\x54\x4c\x28\x30\xfd\x28\x21\x09\x1b\xcb\x8f\x32\xc3\x14\xa7\xdd\x44\x86\x94\xa2\x2a\x88\x35\x79\x4a\x63\x51\xb1\x02\x6c\x5c\xe8\xba\x04\xb3\x75\x96\xaf\x6b\x55\x50\x12\x9c\x95\x5e\x5e\x50\x52\x82\x33\x23\xa5\x7f\xbe\xa0\x9d\x02\x1e\x09\x25\x85\x82\x28\x93\x3a\xbf\xa3\x84\x49\x4c\xe9\x40\x41\xe7\x10\x6c\xa0\x33\x6b\x10\xb5\x9a\x98\x88\xba\x28\x24\x18\x4a\xdc\xfe\x4b\x69\xa0\xa1\x84\x33\x64\xed\x98\x9b\xab\x94\xac\xb6\x40\x09\x33\x82\xb5\x70\x01\x4f\xe9\x8e\xc9\xbe\x57\xb2\xcc\xad\xc5\xad\xe7\x71\x40\x8a\xc2\xaf\xd3\xc0\x28\x42\x12\x5b\xb3\x13\x16\x44\xce\xa9\xe8\x26\x89\x1d\xc9\xc0\xea\x38\x98\xd4\xaf\x41\xcc\xc5\xbe\xf5\x92\x58\xb1\x7d\xb7\xb8\x15\x13\x8a\x18\xed\xcc\x75\x9f\xf4\xf4\x3a\x25\x99\x21\xf1\x68\x49\x05\x77\x3e\xc4\xd0\x6e\x17\x57\x75\xb0\xea\xb5\xd1\x85\x01\xe7\x78\xde\xe7\x52\x1a\x96\xe6\x8a\x5c\x5e\xd4\xf7\xcf\xc7\x53\x5d\x60\x8b\x9c\xd3\x0d\x1b\x91\x45\x23\x6a\xe0\xe3\x4e\xa6\x44\xc5\x10\x38\x6d\x27\xd4\x0d\x66\xcc\x50\x6f\x6c\xd7\xb1\xf5\x3d\xad\x29\xde\x61\xae\xc8\xb3\x8b\x8b\xd5\xf3\x76\x4d\xf6\x4c\x36\xa0\xf4\x21\xa5\xcf\x2e\x2e\x86\x7d\x95\x50\x29\x1d\xf7\xb0\xfb\x40\xb5\xb9\x09\x11\x51\xfc\x47\xa8\x62\xbd\x5e\x0f\x00\x9f\xe0\x3f\x03\x73\x3c\x69\xa3\x0f\x27\x01\xc9\xb5\x8c\x6c\x35\x1a\x9e\x10\x30\xc3\x09\xc2\x3d\x46\x39\x28\x84\x76\xde\xae\x77\xbb\x13\x8a\x0b\x55\xd8\x09\xf7\x9c\x3f\x72\x9b\x7f\x46\xe5\xce\x92\xcb\x11\x99\x0f\x9a\x0b\x0a\xb6\x1e\x18\xba\xb9\x48\xe2\xf2\x72\x41\x4c\x3d\x96\x02\xf7\xb8\x24\xc4\x1d\x16\x74\xf3\x6b\xdb\x4c\xe2\x7a\x66\xf6\x18\xd1\xc5\xae\x79\xc7\x57\x03\x53\xc2\xb7\x44\x52\xc2\x97\xc2\xe8\x24\x74\x18\x4a\xf8\xee\x00\xcc\x75\x55\x09\xfc\x76\x10\xb6\xf2\xbf\x08\xc4\x4e\x46\x80\xf1\x65\x68\x7d\x6f\x40\x1a\xa8\xb5\x15\xa8\x8d\xf8\x86\x0e\x39\x54\xf2\x45\x90\x8e\x04\x05\x5c\xdf\x0f\xba\xbe\x37\x70\x91\x99\x02\xbe\xa1\x97\xb6\xf2\xbf\x08\xd2\x4e\x46\x40\xf3\x36\xb4\xbe\x37\x20\x79\x63\xe6\x59\xcd\xd7\x44\xb2\x53\xd0\x43\x79\x71\xe5\xff\x3d\x06\xd1\x5e\x56\x80\xf4\xba\x6d\x7e\x1d\x4c\x47\xcd\xb6\x31\xce\xb0\xba\x96\x85\xdc\xa9\x0d\x99\x0b\x2b\x60\xe9\x04\x4f\xc6\xb3\xeb\x8e\xcb\xa1\x7a\xa1\xea\x06\xbb\xe9\xee\xb4\xa9\x22\x97\xad\x19\x2d\xc9\xb0\x11\xd9\x8a\xec\xa4\x66\x18\x19\x9f\xbb\xb7\x79\x6d\x40\xa6\x96\x2c\x87\x52\x4b\x0e\x26\xa5\x1f\x80\x99\xbc\x5c\xaf\xd7\x01\xb1\xfe\xc0\xb6\xbe\x7f\x68\x9b\x87\xfe\xd8\x44\x96\x49\xe8\x0c\x09\x0d\xff\xeb\x54\x87\x8f\x52\xef\xc1\x74\x9d\x21\xc3\x0b\x4a\x7c\xd7\x72\x06\x93\xe0\xb1\xc4\x3d\xf6\x99\xd9\x4a\x61\x49\x6c\xae\xeb\x90\x96\xd3\xa1\x4f\xb3\x3c\x78\xe6\x8b\x3c\xac\x32\x96\x67\x30\xd7\x0c\x4b\xba\xf9\x9d\x61\x79\x26\x63\x38\x5c\xba\x63\xe5\x4c\xe6\x3e\x8c\x3e\x0c\xe2\xe7\xc3\x5c\x48\x12\x8f\x91\x70\x14\x13\xb4\x12\x0c\xb5\xde\x88\x68\xdc\x95\xc4\x1e\xff\xa3\xd3\xb6\x9e\xd9\x95\x13\xae\x70\xd8\x24\x3f\x46\x11\x89\xd7\x7d\x25\x40\xa2\xa8\xab\x31\x76\x5a\x23\x98\x4f\x56\x83\xc3\xb0\x11\xbe\xab\xc6\x65\xf2\xa3\x22\x31\xd4\x83\x25\x62\x6d\xaf\xe2\xb8\x10\x58\x36\xd9\x3a\xd7\x55\x3c\x2c\xf1\x5d\xbf\xd1\x19\x25\x21\x2e\xa6\x74\x9b\x49\xa6\xee\xe8\xe6\x58\xda\x11\x61\x09\x73\xa5\xc3\x47\xc8\x91\x64\x0f\x63\xd9\x57\xf1\x48\x9e\x53\x30\x17\x36\xbb\x68\xf0\x72\x7f\xaa\x04\xe7\x1a\x9f\x9f\x6b\x6c\x2c\xac\x6d\xc0\xc6\x0a\x0e\x73\x55\x6e\x7d\x0d\x12\xa6\x88\xa7\x1a\xd4\xa6\xa3\x9a\xae\x03\x39\x34\xc3\x45\xcb\x60\x13\xc7\x08\x55\x2d\x19\xb6\x31\xb3\x6b\x75\x7b\xea\x58\xe5\x21\x5f\xda\x1b\xc7\x65\x58\x11\xb1\x23\x4f\xc2\x5e\x21\x69\x4a\xe8\x1b\xcd\xc5\xee\x81\x3e\x25\xff\x25\xab\x93\x35\x6b\xc6\x78\x01\xc4\xff\x46\xb5\x11\x15\x73\x9e\xfb\xe6\xdd\xf5\xcd\xaf\xff\x9a\x55\xae\x2b\xf2\x3f\x02\xd2\xc2\x54\xd1\x8d\xb2\x60\xf0\x0c\x45\xb6\xc9\x73\x57\x74\x6e\x5e\xbe\x7f\xf5\xe2\xf6\xd5\x67\x2b\xba\x06\x09\x08\x67\x28\xe2\x4c\x15\xae\xf6\xbd\x7e\xf5\xdb\xab\x13\x7a\x56\xc7\x45\x43\x7e\x02\xec\x10\x4b\x92\x5c\x73\x58\xf0\xfb\x3f\xd1\x4d\xb2\x4a\x09\x96\xc2\xae\x5d\xe4\x66\x88\xc0\x5d\x6e\xef\x82\xcf\x93\xa7\x64\xb5\x19\xb9\x86\x97\xf2\x09\x65\x5d\xfc\x09\xea\x7a\x2d\xc9\x2a\x22\x21\x24\xfd\xc3\x48\xb2\xda\xb4\x57\x45\x4a\xeb\x1a\xdc\x3e\x55\xda\xc0\x0e\x8c\xbf\xf9\x98\x38\x6a\x6f\x5d\xa5\x39\xc8\xb5\x2d\xb5\xc1\x20\xea\x35\xb3\x47\x0b\x8f\xa6\x95\x27\x4c\x1b\x46\xb7\x91\x61\xc7\x50\xf7\x08\xe3\x86\xec\xef\x0e\x8e\x7c\xb5\x89\xc7\xdd\x6f\x59\x05\xbd\x95\x9d\x79\x49\x1c\x36\xd3\x63\xb7\xd6\xb6\xd2\x9c\xc9\xc5\xcb\x30\x3f\x12\xb9\x88\x3c\xbe\x39\x29\x7f\x19\x53\x84\x64\xc7\xcf\xe1\xfa\x78\x87\xea\x2d\x2d\x7f\x99\xdf\x54\x8d\xaf\xa4\x3a\x60\xa5\xb6\xd0\x5e\x50\x71\x61\x2b\xd1\x8b\x1f\x5f\x44\xbd\xf4\x74\x73\xb7\xf7\x34\xa5\xe0\x1c\x54\x4a\xd1\xb8\x1c\xeb\x27\x14\x15\xd8\xe7\x67\x5c\x3d\x2d\x4d\x7f\x92\xf0\xb5\x01\xc6\x3b\x92\xb0\xb7\x60\xf1\x3d\x38\x38\xf9\x93\xa7\xf3\x0d\x39\x10\xc6\x24\xb8\x28\xe9\x7e\xa3\x03\x33\xca\x05\xb5\xf6\x1e\xc8\x77\xd2\x4d\x62\xd1\x68\x55\x6c\xde\x6a\x14\x39\x5c\x25\x71\xdb\x26\xb7\xa5\xb0\xc4\x55\xcc\x44\x6a\x7d\x67\x09\x6a\x92\x01\x41\xb0\xfe\x3e\xda\x04\xf5\xb3\x0b\x9d\xd1\xae\x1e\xdb\x92\xa1\x8a\x0a\xa3\x9b\x9a\xf4\x5f\xd3\xfc\x6a\x34\x8d\xc5\x75\x1b\x24\x57\xdb\xbd\x80\xc3\xd6\xb0\x03\x1d\x68\xf0\xb2\x2d\xe4\x5a\x71\x1f\x4d\xdf\xb3\xc3\x14\xf9\x33\x84\x97\x70\xcf\x9b\xaa\xfe\x94\x82\xd7\x70\x4f\x1c\xcd\x5c\xcb\x14\x9a\x51\xa6\xd7\xaa\x89\x2a\x40\x16\xf9\x91\x49\xfe\x66\xa6\xc9\x5b\xe9\xf3\xa9\xab\x85\x74\x06\x79\x17\xaf\xda\xb5\x5b\xde\xd6\xfd\xd2\xc6\xcb\x74\xfd\x3e\xef\xc9\x56\x11\xe9\x42\xa9\x1f\x98\x45\x4f\xb2\x94\x4d\x2d\x99\xfe\xc2\xbf\x49\x9c\x32\xbe\x8f\xae\x81\xcc\xeb\x7a\x84\x92\x37\x60\x2d\x2b\x60\x59\xcb\x31\xd7\x57\x18\x09\x64\x52\xe4\x83\xe0\x8c\xa6\x51\xb9\x73\xe8\x60\x47\x2b\xa9\x8d\xce\x8f\x30\xe5\xe6\xfa\xc4\x5c\xa7\xd9\x6c\x80\x74\x15\x91\x1b\x7e\x84\x78\x4a\xd4\x3a\xeb\xd0\x3d\x05\xdf\xe6\x52\xd4\x99\x66\x86\xcf\xdc\x53\x37\xe8\xaf\xf3\x7b\x37\x0d\x4e\x5b\xb5\x81\xae\x67\xf4\x25\x5e\x38\x44\xbc\x7a\x17\x0d\x06\x87\xb9\x16\x44\x8b\x23\x75\x7f\x9d\xbe\xb4\xa1\xc6\x47\xf8\x1c\xa7\x49\xb6\xec\xc2\xf9\xc9\xbb\xde\x59\xb1\xec\x43\xa2\xbf\xbd\xdb\xda\x5a\x28\x05\x66\xf1\x6e\xbd\x7d\x09\x69\xa5\xb4\x94\x74\xfc\x32\xd2\xf6\xae\x0b\xb1\x6b\xdf\x39\x7e\xd3\xcc\x21\x1a\x42\x5d\xfb\x66\x66\xfb\x42\x6e\xae\x9a\x0e\xcd\x76\x45\xf3\xe6\x94\x84\x51\x69\x3c\x8d\x06\xdd\x53\xc1\x40\x41\xc7\x7a\x6a\x72\xb5\x81\x53\x2c\x6e\x6d\x6a\x03\x7f\x44\xde\xc5\xb3\x29\xf5\x52\xf9\x3d\x5f\x97\x70\x32\x85\x94\xfa\xf4\x43\xcc\xb1\x4e\x21\x83\xbd\x16\xbe\x0f\xfe\x81\xa3\x7b\x08\x5b\x70\x36\x3f\x92\x35\x32\xeb\x9d\x8d\xdc\x8a\xfa\x8a\xfc\xcd\xe8\x83\x05\xd2\xd5\xba\xae\x3c\x69\x6c\xf7\x2e\xba\x20\x87\x19\xa3\x0f\x91\x84\x1d\x1e\x05\x31\xc5\x4f\x93\xb6\xe7\x4f\x4f\xeb\x3a\xc9\x1d\x3c\xd8\xf5\xf4\x20\x1f\x24\x9f\x5d\x80\xfc\x64\xde\xb5\x94\x78\x4d\x37\x6c\x97\xfc\xb7\x47\x73\x7b\x1e\x6d\xfe\x29\xe0\x10\xbc\x4a\xab\x60\xfa\x31\x62\x15\x80\xaf\xb5\x45\x17\xb4\xdb\x30\xd5\xee\x4d\x76\xd2\xd4\xcf\xc8\x60\x3f\xc7\xd8\xe3\xe1\xb7\x64\x6e\xc8\xa0\x3f\xcb\xe0\xe5\x92\x6d\x9c\x57\x4e\xdd\xcf\x59\x97\x09\xc5\xe1\x3e\xa5\xd1\xb3\xce\x02\x2e\x98\xd4\xc5\xf8\x00\xff\xa3\x04\x33\xf0\x90\xd0\x90\x7d\x5a\xc4\x75\xde\x54\xa0\xf0\xc4\x9b\x5d\x20\x6f\xb7\x9d\xf3\x97\xe5\x8d\x33\xbc\xc2\xea\x72\xe3\x10\x87\x3e\xb2\x3d\x0b\x1d\x36\xfe\xf8\xef\x06\xcc\x43\x74\xb9\xbe\x5c\x3f\x5b\x7f\xf4\x9b\xb8\x9b\xfd\xa7\x19\x1b\xc5\xc1\xd8\x5c\x1b\x38\x8b\x2d\x63\xf9\x5d\xa6\xd5\x79\x4c\xb5\xae\x6b\x30\xe7\xe9\xe9\xff\x26\xe0\x1c\xae\xfe\xa0\x39\x8b\xab\x0d\x69\x67\xf1\x0c\x1f\xfe\xa7\x7c\x49\x1c\xae\x79\x92\x38\xfc\xf9\xc8\xff\x03\x00\x00\xff\xff\xa9\xd9\x97\x42\x4f\x22\x00\x00") +var _staticIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x5a\x7b\x6f\xdc\xb8\x11\xff\xff\x3e\x05\x8f\xc5\x1e\x12\xe0\xb4\x72\x6a\x1c\x50\x38\xd2\xa2\x69\x9c\x6b\x0c\x5c\x92\x43\xe2\x16\xe8\x5f\x06\x25\xce\x4a\x8c\x29\x52\x25\x67\xbd\x76\x8b\x7e\xf7\x82\x0f\x69\xf5\xda\x5c\xd6\x49\x80\x00\x81\x23\x52\xf3\xe2\x8f\xc3\xe1\xcc\x68\xb3\x1f\xb9\x2e\xf1\xa1\x05\x52\x63\x23\x37\x3f\x64\xee\x3f\x22\x99\xaa\x72\x0a\x8a\x6e\x7e\x20\x24\xab\x81\x71\xf7\x40\x48\xd6\x00\x32\x52\xd6\xcc\x58\xc0\x9c\xee\x70\x9b\xfc\x85\x0e\x5f\x29\xd6\x40\x4e\xef\x04\xec\x5b\x6d\x90\x92\x52\x2b\x04\x85\x39\xdd\x0b\x8e\x75\xce\xe1\x4e\x94\x90\xf8\xc1\xcf\x44\x28\x81\x82\xc9\xc4\x96\x4c\x42\xfe\xec\x67\x62\x6b\x23\xd4\x6d\x82\x3a\xd9\x0a\xcc\x95\x5e\x10\xcd\xc1\x96\x46\xb4\x28\xb4\x1a\x48\xff\xbb\x40\xa3\x8b\x0b\xf2\xfb\x0e\x51\xa8\x8a\x60\x0d\xe4\x5d\x0b\x8a\x7c\xd0\x3b\x53\x02\x11\x8a\xbc\xfb\x70\xf5\xf6\x7a\x41\x20\xdb\x61\xad\xcd\x40\xd6\x1b\x51\xd6\x0c\x24\x79\x0d\xca\x88\x5b\x0b\x8a\x3c\xf9\x6b\x23\xca\xba\x1b\x3e\xa5\x9b\x1f\x82\x14\x14\x28\x61\x13\x74\x67\x69\x18\xc5\x57\x52\xa8\x5b\x52\x1b\xd8\xe6\x34\xb5\xf8\x20\xc1\xd6\x00\x68\xd3\x42\x6b\xb4\x68\x58\xbb\x2e\xad\xa5\xc4\x80\xcc\xe9\xe1\x7d\x67\xde\x31\x6e\xdd\x82\x12\xa5\x56\xa2\x7c\x14\x7b\x2d\xaa\x5a\x8a\xaa\xc6\x47\x71\xb3\xb6\x95\xa2\x64\x0e\xf9\xe3\xfc\x59\x1a\x9c\xc5\x3d\x16\x9a\x3f\x74\x78\x28\x76\x47\x4a\xc9\xac\xcd\xa9\x62\x77\x05\x33\x24\xfc\x97\xc0\x7d\xcb\x14\x4f\x1a\xde\x4d\x78\x03\x49\x51\x85\x87\x68\x14\x21\x19\x17\xbd\x04\xb7\x55\x4c\x28\x30\xfd\x5b\x42\x32\x36\x96\x9f\x14\x86\x29\x4e\xbb\x85\x0c\x29\x45\x53\x11\x6b\xca\x9c\xa6\xa2\x61\x15\xd8\xb4\xd2\x6d\x0d\xe6\xc6\x59\xbe\x6e\x55\x45\x49\x70\x56\x7a\x7e\x46\x49\x0d\xce\x8c\x9c\xfe\xf9\x8c\x76\x0a\x78\x22\x94\x14\x0a\x92\x42\xea\xf2\x96\x12\x26\x31\xa7\x03\x05\x9d\x43\xb0\x81\xce\x62\x87\xa8\xd5\xc4\x44\xd4\x55\x25\xc1\x50\xe2\xce\x5f\x4e\x03\x0d\x25\x9c\x21\x8b\xef\xdc\x5a\xa5\x64\xad\x05\x4a\x98\x11\x2c\xc2\x05\x3c\xa7\x5b\x26\xfb\x59\xc9\x0a\xb7\x17\xd7\x9e\xc7\x01\x29\x2a\xbf\x4f\x03\xa3\x08\xc9\x6c\xcb\x8e\x58\x90\x38\xa7\xa2\x9b\x2c\x75\x24\x03\xab\xd3\x60\x52\xbf\x07\x29\x17\x77\xd1\x4b\x52\xc5\xee\xba\xcd\x6d\x98\x50\xc4\x68\x67\xae\x7b\xa4\xc7\xf7\x29\x2b\x0c\x49\x47\x5b\x2a\xb8\xf3\x21\x86\xf6\x66\x71\x57\x07\xbb\xde\x1a\x5d\x19\x70\x8e\xe7\x7d\x2e\xa7\x61\x6b\x2e\xc8\xf9\x59\x7b\xff\x7c\xbc\xd4\x05\xb6\xc4\x39\xdd\x70\x90\x58\x34\xa2\x05\x3e\x9e\x64\x4a\x34\x0c\x81\xd3\xb8\xa0\xee\x65\xc1\x0c\xf5\xc6\x76\x13\x37\x7e\x26\x9a\xe2\x1d\xe6\x82\x3c\x3b\x3b\x5b\x3d\x8f\x7b\x72\xc7\xe4\x0e\x94\xde\xe7\xf4\xd9\xd9\xd9\x70\xae\x11\x2a\xa7\xe3\x19\x76\x1f\xa8\x36\x57\x21\x22\x8a\xff\x08\x55\xad\xd7\xeb\x01\xe0\x13\xfc\x67\x60\x8e\x17\x6d\xf4\xfe\x28\x20\xa5\x96\x89\x6d\x46\xaf\x27\x04\xcc\x70\x82\x70\x8f\x49\x09\x0a\x21\xae\xdb\xcd\xde\x6c\x85\xe2\x42\x55\x76\xc2\x3d\xe7\x4f\xdc\xe1\x9f\x51\xb9\xbb\xe4\x7c\x44\xe6\x83\xe6\x82\x82\x1b\x0f\x0c\xdd\x9c\x65\x69\x7d\xbe\x20\xa6\x1d\x4b\x81\x7b\x5c\x12\xe2\x2e\x0b\xba\xf9\x35\x0e\xb3\xb4\x9d\x99\x3d\x46\x74\x71\x6a\x3e\xf1\xd5\xc0\x94\xf0\x2d\x91\x94\xf0\xa5\x30\x3a\x09\x1d\x86\x12\xbe\x3b\x00\x4b\xdd\x34\x02\xbf\x1d\x84\x51\xfe\x17\x81\xd8\xc9\x08\x30\xbe\x0c\xa3\xef\x0d\x48\x03\xad\xb6\x02\xb5\x11\xdf\xd0\x21\x87\x4a\xbe\x08\xd2\x91\xa0\x80\xeb\xfb\xc1\xd4\xf7\x06\x2e\x32\x53\xc1\x37\xf4\xd2\x28\xff\x8b\x20\xed\x64\x04\x34\xaf\xc3\xe8\x7b\x03\x92\xef\xcc\x3c\xab\xf9\x9a\x48\x76\x0a\x7a\x28\xcf\x2e\xfc\xbf\xc7\x20\xda\xcb\x0a\x90\x5e\xc6\xe1\xd7\xc1\x74\x34\x8c\x83\x71\x86\xd5\x8d\x2c\x94\x4e\x6d\xc8\x5c\x58\x05\x4b\x37\x78\x36\x5e\x5d\x77\x5d\x0e\xd5\x0b\xd5\xee\xb0\x5b\xee\x56\x9b\x26\x71\xd9\x9a\xd1\x92\x0c\x07\x89\x6d\xc8\x56\x6a\x86\x89\xf1\xb9\x7b\xcc\x6b\x03\x32\xad\x64\x25\xd4\x5a\x72\x30\x39\xfd\x00\xcc\x94\xf5\x7a\xbd\x0e\x88\xf5\x17\xb6\xf5\xf3\x43\xdb\x3c\xf4\x87\x21\xb2\x42\x42\x67\x48\x18\xf8\xbf\x4e\x75\x78\xa8\xf5\x1d\x98\x6e\x32\x64\x78\x41\x89\x9f\x5a\xce\x60\x32\x3c\x94\xb8\x87\x39\x33\xdb\x29\xac\x89\x2d\x75\x1b\xd2\x72\x3a\xf4\x69\x56\x06\xcf\x7c\x51\x86\x5d\xc6\xfa\x04\xe6\x96\x61\x4d\x37\xbf\x33\xac\x4f\x64\x0c\x97\x4b\x77\xad\x9c\xc8\xdc\x87\xd1\x87\x41\xfc\x7c\x98\x0b\xc9\xd2\x31\x12\x8e\x62\x82\x56\x86\xa1\xd6\x1b\x11\x8d\xa7\xb2\xd4\xe3\x7f\x70\xda\xe8\x99\x5d\x39\xe1\x0a\x87\x4d\xf6\x63\x92\x90\x74\xdd\x57\x02\x24\x49\xba\x1a\x63\xab\x35\x82\xf9\x64\x35\x38\x0c\x1b\xe1\xb9\xd9\xb9\x4c\x7e\x54\x24\x86\x7a\xb0\x46\x6c\xed\x45\x9a\x56\x02\xeb\x5d\xb1\x2e\x75\x93\x0e\x4b\x7c\x37\x6f\x74\x41\x49\x88\x8b\x39\xbd\x29\x24\x53\xb7\x74\x73\x28\xed\x88\xb0\x84\xb9\xd2\xe1\x23\x94\x48\x8a\x87\xb1\xec\x8b\x74\x24\xcf\x29\x98\x0b\x9b\x35\x1a\xbc\xdc\x9f\x1a\xc1\xb9\xc6\xe7\xa7\x1a\x9b\x0a\x6b\x77\x60\x53\x05\xfb\xb9\x2a\xb7\xbf\x06\x09\x53\xc4\x53\x0d\x6a\xd3\x51\x4d\xd7\x81\x1c\x86\xa1\xd1\x32\x38\xc4\x29\x42\xd3\x4a\x86\x31\x66\x76\xa3\xee\x4c\x1d\xaa\x3c\xe4\x4b\x67\xe3\xb0\x0d\x2b\x22\xb6\xe4\x49\x38\x2b\x24\xcf\x09\x7d\xa3\xb9\xd8\x3e\xd0\xa7\xe4\xbf\x64\x75\xb4\x66\x2d\x18\xaf\x80\xf8\xbf\x49\x6b\x44\xc3\x9c\xe7\xbe\x79\x77\x79\xf5\xeb\xbf\x66\x95\xeb\x8a\xfc\x8f\x80\xb4\x30\x55\x74\xa5\x2c\x18\x3c\x41\x91\xdd\x95\xa5\x2b\x3a\x37\x2f\xdf\xbf\x7a\x71\xfd\xea\xb3\x15\x5d\x82\x04\x84\x13\x14\x71\xa6\x2a\x57\xfb\x5e\xbe\xfa\xed\xd5\x11\x3d\xab\xc3\xa6\x21\x3f\x02\x76\x88\x25\x59\xa9\x39\x2c\xf8\xfd\x9f\xe8\x26\x5b\xe5\x04\x6b\x61\xd7\x2e\x72\x33\x44\xe0\x2e\xb7\x77\xc1\xe7\xc9\x53\xb2\xda\x8c\x5c\xc3\x4b\xf9\x84\xb2\x2e\xfe\x04\x75\xbd\x96\x6c\x95\x90\x10\x92\xfe\x61\x24\x59\x6d\x62\xab\x48\x69\xdd\x82\x3b\xa7\x4a\x1b\xd8\x82\xf1\x9d\x8f\x89\xa3\xf6\xd6\x35\x9a\x83\x5c\xdb\x5a\x1b\x0c\xa2\x5e\x33\x7b\xb0\xf0\x60\x5a\x7d\xc4\xb4\x61\x74\x1b\x19\x76\x08\x75\x8f\x30\x6e\xc8\xfe\x6e\xef\xc8\x57\x9b\x74\x3c\xfd\x96\x35\xd0\x5b\xd9\x99\x97\xa5\xe1\x30\x3d\xf6\x68\xdd\x34\x9a\x33\xb9\xd8\x0c\xf3\x6f\x12\x17\x91\xc7\x9d\x93\xfa\x97\x31\x45\x48\x76\xfc\x1a\x2e\x0f\x3d\x54\x6f\x69\xfd\xcb\xbc\x53\x35\x6e\x49\x75\xc0\x4a\x6d\x21\x36\xa8\xb8\xb0\x8d\xe8\xc5\x8f\x1b\x51\x2f\x3d\xdd\xdc\xed\x3d\x4d\x2d\x38\x07\x95\x53\x34\x2e\xc7\xfa\x09\x45\x03\xf6\xf9\x09\xad\xa7\xa5\xe5\x4f\x12\xbe\x18\x60\xbc\x23\x09\x7b\x0d\x16\xdf\x83\x83\x93\x3f\x79\x3a\x3f\x90\x03\x61\x4c\x82\x8b\x92\xee\x6f\xb2\x67\x46\xb9\xa0\x16\xfb\x40\x7e\x92\x6e\x32\x8b\x46\xab\x6a\xf3\x56\xa3\x28\xe1\x22\x4b\xe3\x98\x5c\xd7\xc2\x12\x57\x31\x13\xa9\xf5\xad\x25\xa8\x49\x01\x04\xc1\xfa\x7e\xb4\x09\xea\x67\x0d\x9d\xd1\xa9\x1e\xdb\x52\xa0\x4a\x2a\xa3\x77\x2d\xe9\x9f\xa6\xf9\xd5\x68\x19\x8b\xfb\x36\x48\xae\x6e\xee\x04\xec\x6f\x0c\xdb\xd3\x81\x06\x2f\xdb\x42\xa9\x15\xf7\xd1\xf4\x3d\xdb\x4f\x91\x3f\x41\x78\x0d\xf7\x7c\xd7\xb4\x9f\x52\xf0\x1a\xee\x89\xa3\x99\x6b\x99\x42\x33\xca\xf4\xa2\x9a\xa4\x01\x64\x89\x7f\x33\xc9\xdf\xcc\x34\x79\xab\x7d\x3e\x75\xb1\x90\xce\x20\xef\xe2\x55\xdc\xbb\xe5\x63\xdd\x6f\x6d\xba\x4c\xd7\x9f\xf3\x9e\x6c\x95\x90\x2e\x94\xfa\x17\xb3\xe8\x49\x96\xb2\xa9\x25\xd3\x5f\xf8\x6f\x12\xc7\x8c\xef\xa3\x6b\x20\xf3\xba\x1e\xa1\xe4\x0d\x58\xcb\x2a\x58\xd6\x72\xc8\xf5\x15\x26\x02\x99\x14\xe5\x20\x38\xa3\xd9\xa9\xd2\x39\x74\xb0\x23\x4a\x8a\xd1\xf9\x11\xa6\x5c\x5d\x1e\x59\xeb\x34\x9b\x0d\x90\xae\x12\x72\xc5\x0f\x10\x4f\x89\xa2\xb3\x0e\xdd\x53\xf0\x9b\x52\x8a\xb6\xd0\xcc\xf0\x99\x7b\xea\x1d\xfa\x76\x7e\xef\xa6\xc1\x69\x9b\x18\xe8\x7a\x46\x5f\xe2\x85\x4b\xc4\xab\x77\xd1\x60\x70\x99\x6b\x41\xb4\x38\x50\xf7\xed\xf4\xa5\x03\x35\xbe\xc2\xe7\x38\x4d\xb2\x65\x17\xce\x8f\xf6\x7a\x67\xc5\xb2\x0f\x89\xbe\x7b\x77\x63\x5b\xa1\x14\x98\xc5\xde\x7a\xfc\x12\x12\xa5\x44\x4a\x3a\xfe\x32\x12\x67\xd7\x95\xd8\xc6\xef\x1c\xbf\x69\xe6\x10\x0d\xa1\x2e\x7e\x33\xb3\x7d\x21\x37\x57\x4d\x87\x66\xbb\xa2\x79\x73\x4c\xc2\xa8\x34\x9e\x46\x83\xee\x53\xc1\x40\x41\xc7\x7a\x6c\x71\xad\x81\x63\x2c\x6e\x6f\x5a\x03\x7f\x44\xde\xc5\xb3\x29\xf5\x52\xf9\x3d\xdf\x97\x70\x33\x85\x94\xfa\xf8\x87\x98\x43\x9d\x42\x06\x67\x2d\x3c\xef\xfd\x07\x8e\xee\x43\xd8\x82\xb3\xf9\x37\xc5\x4e\x16\xbd\xb3\x91\x6b\xd1\x5e\x90\xbf\x19\xbd\xb7\x40\xba\x5a\xd7\x95\x27\x3b\xdb\x7d\x17\x5d\x90\xc3\x8c\xd1\xfb\x44\xc2\x16\x0f\x82\x98\xe2\xc7\x49\xe3\xfd\xd3\xd3\xba\x49\x72\x0b\x0f\x76\x3d\xbd\xc8\x7d\xf2\xe9\x70\x75\x37\x44\xe2\x60\xa5\x83\x64\xcc\xc5\xcb\x4f\xa6\x61\x4b\x79\xd8\xf4\xfc\x76\xb5\x40\xbc\xa9\xe3\xf5\xb4\xf9\xa7\x80\x7d\x70\x32\xad\xc2\x4a\x0e\x01\xac\x02\x7c\xad\x2d\xba\x18\x1e\xa3\x56\x3c\xaa\xec\x98\xe5\x31\xd5\x3d\x2d\xc3\xfd\x1c\xeb\x0f\x97\xe3\x92\xfd\x41\xed\x67\xad\x60\xb9\xa4\x1b\xe7\x9d\x53\xf7\x74\xd6\x15\x42\x71\xb8\xcf\x69\xf2\xac\xb3\x80\x0b\x26\x75\x35\xbe\xe0\xff\x28\x01\x0d\x3c\x24\x0c\x64\x9f\x36\x71\x5d\xee\x1a\x50\x78\xe4\x9b\x5e\x20\x8f\xc7\xd2\xf9\xd3\xf2\xc1\x1a\xb6\xb8\xba\xdc\x39\xc4\xa9\x8f\xec\x8e\x85\x09\x9b\x7e\xfc\xf7\x0e\xcc\x43\x72\xbe\x3e\x5f\x3f\x5b\x7f\xf4\x87\xbc\x5b\xfd\xa7\x19\x77\x8a\x83\xb1\xa5\x36\x70\x12\x5b\xc1\xca\xdb\x42\xab\xd3\x98\x5a\xdd\xb6\x60\x4e\xd3\xd3\xff\x66\xe0\x14\xae\xfe\x22\x3a\x89\x2b\x86\xbc\x93\x78\x86\x3f\x0c\x98\xf2\x65\x69\x68\x03\x65\x69\xf8\x79\xc9\xff\x03\x00\x00\xff\xff\x6a\x3a\x07\xd9\x6f\x22\x00\x00") func staticIndexHtmlBytes() ([]byte, error) { return bindataRead( @@ -276,12 +276,12 @@ func staticIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/index.html", size: 8783, mode: os.FileMode(420), modTime: time.Unix(1583787593, 0)} + info := bindataFileInfo{name: "static/index.html", size: 8815, mode: os.FileMode(420), modTime: time.Unix(1583790160, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _staticJavascriptsApplicationJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x5b\xef\x72\xdb\x38\x92\xff\x9e\xa7\xc0\x62\x3c\x67\x32\xa6\x28\xd9\x77\x99\x9d\xa1\xa3\xe4\x3c\xce\x3f\x5f\xcd\x64\x52\x49\xe6\xae\xea\x2c\xaf\x0f\x22\x5b\x12\xc6\x14\xa8\x02\x20\x4b\x4e\xac\xab\x7d\x9a\x7d\xb0\x79\x92\x2b\x80\x00\x09\x90\x94\x2c\xe7\x3e\xec\xd4\x96\x23\x02\xdd\xbf\x6e\x34\x80\xfe\x47\xee\x2d\xe1\xe8\x93\x24\x52\xa0\x21\xfa\x99\xa4\x37\xe3\x82\x41\xfc\x6b\x91\x41\x1e\xc3\x5a\x02\xcb\x82\xaf\x4f\x10\x5a\xf2\x3c\x41\xb8\x2f\x14\x21\x8e\x9e\x20\x94\xc1\x84\x2c\x73\x29\x12\xa4\xa6\x11\xc2\x0a\x63\x29\x70\x82\xcc\x7f\x98\x32\x2a\x29\xc9\xe9\x17\xca\xa6\x9a\xa5\x24\xe2\x12\xb2\x33\x69\xe8\xd8\x32\xcf\xcd\xd4\x1b\xca\xa8\x98\xd5\x73\xce\xd4\x07\x5e\x4c\x39\x88\x0a\x7c\x60\xc6\x3f\x13\x3e\x05\x59\xcb\xb4\xe3\x1f\x61\x51\x08\x2a\x0b\x4e\x41\x4f\xda\xf1\xf3\x62\x3e\xa7\x1d\xf4\x6f\x68\x0e\x8e\xe6\xce\x38\xcb\x28\x9b\xfa\x72\x37\xea\x0f\x15\x56\xdd\x04\x4d\x96\x2c\x95\xb4\x60\x41\x68\x4c\xc1\x41\x2e\x39\x43\x72\x46\x45\x3c\x05\x19\x58\xd3\x84\x68\x38\x1c\x22\x3c\x31\x9c\xf8\xd4\xa2\x65\x4b\x4e\x14\x42\x07\x16\x9d\xa0\xc0\x03\x32\xe6\x2b\xb1\x94\x8d\x2c\x65\x25\x17\x0f\x06\x89\xfe\x9f\x16\x80\xd0\x46\xff\x55\xdb\x0c\x2c\x3b\xad\x1e\x84\xc2\x42\x43\xf4\x8a\x48\x88\x17\x84\x0b\xe8\x16\x14\x9e\xfa\x8a\xd4\x4b\x0f\xc2\x5a\x36\xb0\x6c\x1b\x96\xb3\xb1\x16\x6c\x83\x20\x17\xd0\xc5\xcc\x8a\x55\x10\x36\xf5\x9e\xd3\x3c\xa7\x02\x21\x34\xd4\xa4\xbd\x52\x77\x67\x29\x90\x16\x2c\x13\x6a\xfe\x57\x22\x67\xf1\x24\x2f\x0a\x1e\x18\xae\x3e\x3a\x1e\x0c\x06\x61\x4d\xad\x8c\xa6\x64\xa1\x21\x62\xb0\xd2\x62\x03\x6d\xc8\x92\xc4\x4e\xc7\x02\xe4\xa7\x12\x38\x30\x02\x0c\x85\xb1\x73\x45\x28\x8b\x8b\x4f\xbf\x7d\x92\x9c\xb2\x69\x10\xc6\x62\x39\x16\x92\x07\xc7\xc7\x11\xfa\x31\x34\x5b\xbc\x09\x4f\x9f\xac\x28\xcb\x8a\x55\x2c\xcc\x55\x53\xa2\xf5\xb5\x3b\x7d\xf2\x44\x69\x65\xce\xda\xce\x4b\x48\xb3\x33\x29\x39\x1d\x2f\x25\x24\x08\x5f\x64\xfa\x56\x49\x10\x52\x1d\xe0\x0b\x96\xd1\x94\xc8\x82\x8b\x04\x5d\x62\x35\x8a\x23\x84\xaf\xc5\x02\x52\xf5\x63\x42\xd7\x72\xc9\x41\xfd\x9c\x17\xe9\x8d\xfa\x57\xc8\xe5\x58\x4f\x91\x1b\x3d\x9e\xc1\xbc\xd0\xe3\x64\xbe\xc8\x01\x5f\x29\x74\x31\x2b\xb8\x2c\xef\xcd\x3b\x22\x66\xfb\x9c\xf6\x9a\x1a\x57\xd6\x18\x44\xe8\xaf\x61\x75\xde\x25\xa7\xf3\x39\x64\x25\xe1\xaf\x20\x04\x99\x42\x07\xb2\xde\xfa\x72\x16\x0d\x5b\x02\x0c\x9f\x92\xb1\xc8\xa9\x0c\x70\x4f\xfd\xf7\xfa\xfd\x2b\xf4\xe1\xed\x07\xf4\xe9\xe2\xed\xfb\xb3\xcf\xbf\x7f\x7c\xad\x47\x71\x84\x4e\xc2\x78\x51\x2c\x02\x7f\x0b\x0d\x7a\xcc\x61\x91\x93\x14\x82\xfe\xdf\x46\x62\x24\x9e\xf6\x23\x84\x71\x58\x8f\xea\xc1\x83\x72\xb4\xf6\x00\x9f\x41\xc8\x8f\x90\x13\xd9\xe9\x04\x94\xf2\x0b\x22\x67\x9e\xe6\x6a\x9f\x3e\x10\xa9\x0c\x23\x8b\x5f\x8a\x15\xf0\x73\x22\xc0\x2a\x35\x29\x38\x0a\x14\x1f\x45\x43\x34\x38\x45\x14\x3d\x2f\x79\xdb\x5b\x1c\xe7\xc0\xa6\x72\x76\x8a\xe8\xd1\x51\x7d\x09\xd5\x1d\x55\x32\x63\xca\x32\x58\xff\x36\x09\xb6\x70\x5f\xd2\xab\x10\xbd\x40\xbd\xe3\x9a\xb5\xde\x47\xbe\x84\x53\x33\xb8\x71\xee\xa1\x99\x9e\x90\x5c\x40\xb5\x91\x13\x9a\xc3\x79\xc1\x24\x30\x29\x7e\x57\x11\x62\xdb\xe9\xb8\xc4\xfd\x89\x76\xb2\x91\x63\x8d\xca\x4d\xdf\xfd\xb6\x62\xc0\x71\xd8\x3d\xf9\x9e\xcc\xc1\x9f\x73\x4f\x58\xd4\x69\xde\xab\xf8\x8f\x82\xb2\x00\xf7\x71\xd8\xa9\xac\xa3\x69\x4a\xf2\x7c\x4c\xd2\x9b\x08\x01\xe7\x05\xb7\x8a\x1f\xc4\xe4\x0f\xb2\x0e\xac\x7d\x74\xfc\xd3\x92\x1a\x6b\x0e\xc2\xc8\x90\x88\x65\x9a\x82\x10\x09\xaa\x10\xad\x7b\x53\xb8\x49\xf9\x4f\x69\x51\xd7\x2f\xb8\xb7\xdf\x8b\xc1\xe7\x45\x9e\x83\xd6\xb1\x23\x10\x4f\x6c\x68\x52\x42\xe6\xca\x51\x24\x16\xc4\xc0\x1a\x7f\x33\xa9\x91\x95\xcb\xb1\x82\x02\x2b\x59\xfb\xa0\xff\xa4\xb0\x72\x45\xab\x67\xdf\xf1\x24\xca\x5d\x10\x29\xae\xd3\x82\x49\x42\xd5\x76\x39\x92\xf5\x94\x7a\x5e\x14\x79\x4e\xd9\xf4\x33\x4d\x6f\x80\x27\x55\x0c\xb7\x01\xae\x39\x6e\xc8\x2f\x98\x04\x7e\x4b\xf2\x04\x3d\x1b\xe8\x18\x5b\xa5\x0e\x5d\x6e\x41\xef\x42\x4e\x85\x04\xf6\xb9\x28\x8f\xb8\x56\x23\x42\x38\x9d\x11\x36\x05\x7b\xc8\x38\xb0\x0c\x78\x58\x33\xe9\xb0\xf1\xca\xd3\xc5\xde\xbd\x7a\xfe\x43\xa9\x53\x50\x1f\x9c\x12\x67\x57\x70\xd6\xf2\xb7\x44\x46\x83\x5c\x2c\x3c\x60\x6f\xa6\x5b\xa5\x4d\x97\x8c\x19\x11\xe7\x7a\x91\x59\x50\xa7\x45\x4d\x69\xcb\x45\x46\x24\xd8\xe9\xbd\xf1\xaa\x74\xa7\x1b\xcf\x3d\x3a\x7b\xe2\xa9\x1b\xbf\x0d\x2c\x87\xfd\x91\x6c\xe2\xd6\x8d\x65\x66\xf7\x46\xf3\xd2\xc3\x6e\x48\x97\x64\x6f\x5c\x9b\x8e\x76\x43\x9a\x59\x17\x4d\x9f\x2e\xf7\xd0\x6d\x3b\xed\xde\xb5\x42\x43\x24\x40\xda\x3b\x13\xb4\x38\x50\x79\x1d\xf5\x1d\x2e\x95\x9c\x80\x4c\x67\x95\xe0\xc8\xc3\xb4\x38\xf5\x71\x77\xce\xea\xae\x33\xef\xeb\xf4\x97\x56\x36\x9a\xe6\x40\x78\xa5\x65\x9b\xa5\xd3\x0e\xaf\x1a\x8e\xa2\xdb\x1c\x3e\xd5\x63\xec\x51\x6e\x85\xe5\x0f\x42\x6b\x91\x2a\x45\xac\x2c\xb0\x9f\x26\x4d\xbc\x46\xae\xec\xfb\xbd\xfd\x8c\xe4\xf3\x34\xad\xe4\x0b\xec\x50\xeb\x20\xc0\xdf\xa5\x84\x67\xd7\x16\xe7\xfa\x96\xe4\x4b\x95\x26\x49\x58\x4b\xf7\xe8\x66\x95\xd6\xf5\xca\x7d\xcf\xb1\x25\xad\x11\xba\x9c\xb1\x89\x4d\xf9\xf4\xb9\x78\xb7\x9c\x93\xca\x02\x07\x01\x96\x54\xe6\x95\x58\xfc\x96\x4a\x5e\x8c\x13\x84\xd1\x91\xe1\xaf\x29\xbf\x5b\x18\x79\xd7\x63\xc2\x2d\x87\x21\x8a\x53\x21\x02\xbc\xa2\x99\x9c\x59\xb7\x5e\x6a\xaf\x03\x7e\xed\x01\xd1\x11\xc2\xdf\xe3\xa6\xfd\x77\xf9\xe5\x0e\xc1\x1c\xe6\xc5\x2d\x9c\xe7\x44\xc9\xb4\x73\xbd\x31\xe1\x3d\xc2\xe8\x5c\x25\x7a\xc8\x1b\x15\x92\xd3\x05\x64\xb8\xa1\x25\x3e\x1e\x0c\x2a\x5d\x1a\x3b\x67\x9d\xe8\xae\x9d\xb3\x21\xbb\xda\xb9\x19\xcd\x20\x68\x6f\xa0\xad\xae\x8c\xd3\xd6\x29\x65\x4a\x72\xb0\xa5\x48\x18\x4f\x48\x06\x17\x2c\xc0\x13\x22\x24\x6e\xee\xb2\x76\xc1\xbb\xf5\xc8\x61\x5f\x25\xb4\xa7\x7f\xac\x06\xc6\x71\xef\xd2\x21\x2d\x49\xf6\xd2\xa2\x8a\x12\x8f\xd5\xc3\xf5\xf6\xbb\x94\xe1\x0e\xdd\x5e\x1a\xf9\x91\xe6\xb1\x6a\x99\x88\xb1\x4b\x23\x59\x92\xec\xa5\x4c\x15\x9e\xf6\xd7\xc3\xbb\xdb\x3b\xbd\x41\x79\xd8\xc5\x8a\xaa\x48\xd3\x94\x6c\xfb\x1f\x96\x2d\x25\x02\x1a\xfd\xa1\xc4\x71\xd5\xda\xb7\xe0\x0b\x77\xda\xa6\x4c\x63\x0e\xe4\xe6\xd4\x01\x99\x12\x39\x03\xde\x8d\xf0\xd6\xce\x21\x77\xe3\xb6\x63\x11\x46\xf2\xbb\x2d\xda\x9c\xd9\x39\x1f\x6b\x1b\x54\xd5\xe3\x69\x23\xbd\x71\xdb\x3f\x0d\x66\xd3\x54\x6b\x33\xfd\xce\x6e\x58\xb1\x62\x5d\x3c\x5e\x79\x66\x38\x8e\x10\x46\x81\x72\xb5\xba\x17\x73\xc1\xda\x87\xc1\xcd\x1d\x95\xeb\x0c\xcb\x6e\x54\xab\x53\x61\x2a\x83\xaa\x5b\xa1\x9e\x83\xaf\x2a\xe7\x57\x67\xb0\x59\x12\x84\xcd\x82\xe6\xa1\xc2\x42\x92\xa9\x2a\xef\x12\x84\x65\x59\x50\xc0\x6d\x59\x9e\x99\xa6\x62\x9a\xd3\xf4\x06\xc9\x2c\x4e\x8b\xbc\xa7\xcb\x69\x82\x55\x29\x32\x2b\x56\x46\x02\xae\x7a\x72\x12\xe6\x0b\x55\x8d\x27\xe8\x3a\xb6\xbf\x03\xa5\xa5\x7d\xb0\x8e\x55\xdd\x13\x39\xcf\x83\x30\xdc\x99\xdd\x6b\x93\x1d\xa8\x1c\x4f\x11\x9b\x52\xda\xc0\x3a\xe6\x24\xb6\x25\x23\xc2\x30\xce\x88\x24\x01\xb6\x72\xdc\x80\xb5\x2d\x34\x39\x5d\x84\x56\xd5\xa0\x84\x93\x2c\x33\x01\x49\xd5\xf1\x3d\x5e\x92\xe2\xb0\x63\xf3\x15\x4f\x5d\xed\x16\x7c\x4e\xa4\x84\xcc\x16\xc5\xdb\xae\xef\x22\xa7\x52\x20\xdd\x5c\xeb\x72\xeb\xa6\x5d\x61\x7a\x2c\x7d\xec\x34\xd2\x54\x7c\x60\x64\x0e\x8a\xb5\x84\x71\x5b\x2c\x8a\x22\xa3\x1c\x52\x55\xc3\x5b\x70\xc8\x73\xba\x10\x54\xd0\x2f\x10\x18\x96\xaa\x50\x8f\xd0\x0f\x83\x08\x9d\x3c\x73\x2c\xe5\xf0\x0f\x87\x08\xe3\x76\xaf\xf3\xb9\x90\xbc\x60\xd3\x17\xea\xb0\x5f\xc7\x20\x52\xb2\x80\xc0\x2a\xa6\x8f\xf6\xf3\xbe\x25\xe9\x30\x59\xc5\x52\x49\xd2\x3c\x7d\xac\x39\x1f\x89\xad\xed\xee\xac\xd0\xb1\xb8\x90\x3c\x42\x73\xca\x7e\xd1\x6d\x9b\x08\x41\x36\x85\xf2\xb7\x5d\x92\x90\x2a\x91\x35\x1e\x59\x48\xee\x58\x41\x48\x6e\xfa\x3d\xe8\x79\x0d\x82\xee\xef\x91\x3b\x33\x44\x41\x8d\x8a\x9e\xa2\x93\xb0\x65\x2d\x21\x79\xab\x25\x9c\x4d\xa1\xa4\x19\xa2\x33\xce\xc9\x9d\x0b\x72\x84\x8e\x43\xb3\x3f\xb1\xbb\xf1\x73\x9a\x19\x8a\xa1\xab\x42\x0f\xf9\x0a\x9c\xba\x8d\x30\x09\x9c\x69\x29\x58\x3b\x26\x2d\xf7\x08\xe1\x30\xfe\xaa\x1e\x6b\xc4\x23\x84\x37\x3e\x05\x3e\xf5\x3d\x1c\xaf\x1a\x73\xca\x2b\x7d\x84\xe9\xeb\xf5\x22\x30\x12\xc2\x08\xe1\x83\xe3\x3f\xff\xfe\x8f\x83\x13\x37\x8c\xd5\xee\xc2\xd9\x13\xb0\xf6\x81\x78\xc1\xb5\xdf\x79\x55\xba\x5f\xaf\x27\x30\x27\xfc\xe6\x4c\x7c\x82\x1c\x52\x7d\x45\x1d\x2b\x14\x19\xc9\x1d\xff\x68\x24\xfc\xaa\x86\xab\xbe\x91\x69\x90\x38\x5d\x0a\xdb\x14\xca\x13\x84\xbf\x33\x9e\xe2\x5a\x63\xa1\x58\xff\xd3\x4b\xcb\xee\x12\x76\x7a\x45\xa8\x96\x66\xda\x1a\x4e\xa6\xed\xa3\xe0\xb0\x84\x09\x5a\x8c\xba\x0c\x7c\xe3\xb4\xaf\x9c\x1e\x87\xbf\xcc\x5d\xde\x30\xcd\x0b\x01\x42\x06\x58\x8e\x8b\xec\x0e\x87\xba\xc3\x14\x60\xc9\x63\x49\xc6\x39\xf4\x84\xc1\x68\xe6\xd3\xcd\xd9\xd3\x27\xdb\xfc\x5c\x07\x61\x57\xaf\xec\xa1\xd8\x92\x56\xfd\xb3\x04\xd9\x94\xfa\x5b\x9a\x4b\x35\x4e\x84\x30\xc9\x32\xbf\xbd\x64\xb4\x71\x97\x53\xb1\x97\x6d\x31\xdb\x96\x4a\xaa\x54\x3d\x42\xd7\x71\x06\xe3\x62\xc9\x52\x13\x4a\xca\x84\x2f\x42\xcf\x06\x83\xb0\xbd\xb1\xe2\x5a\x00\xe1\xa9\xf2\xc3\x05\x0b\xf0\x0d\xdc\x2d\x17\x1d\x20\x25\x91\x95\x12\xa1\x93\x4e\xb0\xea\x94\x28\x28\x75\x33\xe2\xb1\x28\x4f\x0c\x8e\x9c\xcb\xa1\xee\x83\x5b\x2c\x65\x45\xba\x9c\xab\x31\xab\x42\xa6\xf2\x91\xa8\xe3\x3a\x39\x89\x20\xc4\x37\x70\x77\x5e\x64\xde\x9c\xce\x90\xfe\xf5\xaf\x49\x35\x60\xa3\x89\x7d\x01\x32\x71\x36\x58\x5f\x4d\x5a\x2c\x85\x59\x56\xdd\x3d\x6b\xa4\x41\x35\xf2\x4f\x7b\x22\x33\x58\xcb\x7d\x50\x1b\x49\x59\xed\x8b\x6a\x92\x4d\xf5\x4b\xf9\x6b\x23\xc5\xba\x45\x15\xba\x06\xae\x01\x76\xf1\x7b\x1a\x92\x54\xd2\x5b\xa8\x74\xdc\xe7\x3e\x39\x18\x0f\x5c\xa9\xda\x3e\x7b\x39\x32\xc7\x99\x59\x7c\x3f\xd9\xa9\xfa\xe0\x8f\xf1\x6e\xae\x87\xdb\xe5\xe5\xb6\x9d\x61\xcf\xd3\xa1\x7d\xbc\x9d\x2b\x71\x53\xf6\x7f\xf4\x89\x9e\xd1\x2c\x03\xf6\xd8\xbb\xb0\x64\x63\xed\xfd\xec\x7d\xa8\x80\x1b\xa5\xdc\x36\x4f\x53\xfb\x16\xb7\x49\xe7\x74\x9d\xdb\x61\xcb\x98\xc0\xcd\xe1\xcc\xd0\xeb\xdc\xdf\xc0\x32\x57\xf7\x37\x6d\x13\x56\x96\x8d\x21\xb7\xce\xa1\x02\x08\x63\xb2\x58\x00\xcb\xac\xef\x3b\x00\xa7\x31\xe8\x1d\xc7\x07\xde\x04\x2a\x97\xbe\x35\x30\x54\x88\xce\x15\x7c\x00\xaf\x79\x15\x14\xe7\x59\x9e\x2b\x78\x1c\xc6\xac\x90\x01\x8e\xb3\x1e\x2b\x18\xe8\x88\xc4\x85\x74\x4c\xd9\xf0\x21\x8f\x14\xa5\xb8\xf7\x16\xe5\xfb\xe0\x2d\x39\x37\x03\xc8\x72\x40\x43\x74\x10\x4b\x4e\xe7\x41\xb7\xab\xbf\x55\x27\xbb\xf3\x45\xa1\x72\x32\x16\xc3\x4f\x8b\x75\xb9\xa3\xac\x5d\x35\x91\x90\x0e\xd3\x48\xb6\xfa\x5b\x76\x09\xa7\x4f\xda\x3e\x69\xf3\xe4\x61\x30\x20\xe9\xac\xab\xd9\xea\xbc\xf8\x3c\xd0\x87\xa8\xca\x10\xea\x2a\xce\x36\xf9\x3a\x57\x57\x42\x94\x8d\x9f\x6d\x20\xe5\xec\x1e\x30\x55\xb5\x7e\xb7\x0d\xaa\xa6\x78\x00\xae\xf5\x76\xb5\xdc\x82\xf2\x4d\xaa\xca\xc6\x4b\xa5\xb6\x4e\xd7\x82\x3a\x49\xdc\x08\x61\x15\xdd\xb9\x61\x8d\x8f\x28\x6a\xae\xda\xef\xb7\x58\x5c\xef\xe4\xd6\xfa\x13\x3f\xa7\x72\xdf\x14\xd6\x15\x7f\xf7\x69\xc0\xcd\xc4\x4c\xc7\x8d\x9d\x45\xff\xbe\x85\x7a\xe5\xe6\x9d\x72\x9d\x32\x09\x1c\x84\xa4\x6c\x5a\x16\x4b\x1f\xca\xcc\x5f\x24\xe8\x52\xaf\xae\x1f\x04\x27\xcf\x2e\x07\xbd\x67\x57\xf7\x27\x97\x83\xde\xbf\x5d\x5d\x0e\x7a\x3f\x5d\xdd\x5f\x0e\x8e\xaf\x5e\xea\x9f\xfa\xcf\xcb\x70\x14\xff\x73\xe8\xc2\xfe\x74\x4e\x23\xa3\xea\x25\xe9\x7d\x39\xeb\xfd\xf7\xa0\xf7\x53\xfc\x97\xef\x0e\xbe\xff\x97\xa7\x47\xfd\xe1\xcb\xbf\x5d\xff\xcf\xd7\xfb\xcd\xff\xf6\xae\x8e\xfe\xbd\x9e\xbf\x0a\x5e\x26\xf5\x53\xef\xea\xeb\x20\xfa\xe1\x78\xe3\xcc\x87\x2f\x83\x97\xc9\x28\x7e\x14\x47\xf8\xd4\xd3\x26\x18\xad\x9e\x26\xa3\xfe\xa8\x1f\x06\x97\xa3\x8c\xf4\xbe\x8c\xe2\xde\xd5\x91\x5a\xd9\xa5\x7e\xb8\xfa\x7a\x12\xfd\xb0\x69\xad\x60\x32\xe8\xfd\x34\xea\x8d\x0e\x46\xfd\xab\xaf\x27\x83\x68\xe3\xcd\x2f\x05\x70\x5d\x2f\xbb\x83\x02\x52\x0e\xd2\x1b\x5a\x10\x21\x56\x41\xc1\xc3\x97\x99\x37\x9e\x72\xc8\x02\x71\x0f\x4c\xe5\xec\xbe\x68\xa2\xdf\xb7\x07\xd7\xf7\xbd\xfb\x38\x7c\x29\x8b\x1b\x60\xd5\xfc\xd5\xd6\x66\x52\x95\x43\xdc\x52\x58\x5d\x73\xb2\xb2\x0d\xa5\x8f\x64\x65\x53\x05\xfb\xb9\x5a\x17\xc7\x0c\xd6\xd9\x72\xbe\xb0\x5c\xef\x60\xfd\x6a\x39\x5f\x78\x9c\xbb\xdf\x1a\x7f\x43\x5f\xc9\x7c\x99\x04\x2b\x74\x9e\xd3\xc5\xb8\x20\x3c\xfb\x8f\x4f\xc1\x61\x3c\x96\xec\x30\xaa\x5f\x26\xd9\x3e\x5c\x82\x6c\x86\x12\x4f\x41\xbe\xce\x41\xfd\xfc\xf9\xee\x22\x0b\x0e\xbd\x9b\x75\x18\x7a\x25\x66\x57\x1b\xa9\x61\x98\x2d\xbd\xe8\x96\x49\x5d\x27\x54\xc6\x53\xdc\x51\x89\x78\xf6\x6c\x78\xbb\x36\x97\x56\x59\xbf\x94\x70\x78\xca\x86\x77\x27\x51\x6a\xb7\x24\x8c\xd5\x2a\x02\xbf\x1f\xd0\xd8\xb7\xfd\x17\xf6\x80\x96\x5b\xd6\xb6\xcb\x1c\xdd\x3a\xef\x58\x59\x0d\xdb\x58\xd8\x14\xe4\xbb\x42\xc8\xb2\xa5\xfa\xd0\xe7\x0a\xce\x3b\x93\xdf\xb9\xf2\xb2\x36\x2a\xe1\x29\x95\xb3\xe5\x18\x87\xfa\x85\xa5\x8a\x4c\xb6\xdb\xf6\xb6\x9c\xf0\x8e\x8b\x1a\xfc\x85\x8c\xb1\xf3\x75\xd7\x92\xa5\x44\x7e\xd3\xf7\x5d\xa5\x66\x5d\xdf\x87\xb9\xb9\x8f\xfd\x6c\xab\x6e\x7d\x1d\x3f\x1b\xb4\xba\x5d\x55\xcb\xce\x90\x77\xb5\x4c\x9b\x34\xce\xf7\x6a\x0a\x52\x37\xf9\xfe\xfc\xfb\x3f\xea\xc5\x3d\xf4\xd9\x97\x9b\x48\x76\xb6\x78\x1d\xa4\x9f\x29\x23\xfc\xce\x01\x51\x05\x55\x03\xa8\x7f\x39\x5a\x0f\x06\xbd\xd1\x7a\xf0\xe3\x68\x3d\x78\xdd\x1b\xad\x8f\xdf\x5c\xf5\xf5\x37\x5d\x25\x79\x85\x37\xa3\xd3\x59\x4e\xa7\xb3\xf2\x4d\xb8\x1b\x21\xdd\xc3\x3d\x23\x77\x42\x92\xf4\xc6\x73\x46\x5b\x63\x6a\x3c\x29\xf8\x6b\x2f\xcf\xb3\x7d\xb6\xca\xd8\x16\x10\x0d\xab\x9f\x55\x7f\xce\x10\x47\x08\x3f\x9f\x13\x7e\xf3\xe2\xe0\xf8\x79\x5f\xff\xf0\xeb\xa4\x6a\xb1\x16\xa0\x6e\x62\x37\x6b\xb8\x3d\x4f\xf5\x99\x26\xd1\x1f\xc9\x22\xfc\x0a\x72\x90\x80\xfd\x0c\xd5\x5c\x34\x34\x44\xf8\x79\x46\x6f\x51\xaa\x2e\xe7\xf0\x90\xe4\xc0\x25\xd2\x7f\x7b\x94\x4d\x8a\x43\xc4\x8b\x1c\xcc\xf8\xe1\x0b\x9d\x1e\x99\xcc\xb4\x60\xe8\x7b\x81\x64\x81\x04\x80\x85\x13\xa8\x98\xa0\x4c\xcb\xcb\x74\x7b\x5c\xc4\xcf\xfb\x19\xbd\x7d\x81\xdd\x9c\x74\x56\x08\xe9\x7c\x16\x68\x6f\xac\x9f\xb8\x96\x6f\xe4\xde\x2c\x59\x8a\x86\xed\x45\xef\x70\x1d\xee\xeb\x98\x32\xc4\x98\x99\x6a\x5b\xf0\xf7\x02\x47\x5a\x8b\xce\x37\x7f\xc8\x69\x48\x34\x64\x88\x05\x65\x0c\xb8\x27\x42\x01\xfc\xb6\x94\x06\x21\x72\x14\x0f\xc2\x1d\xc5\x45\xb5\x09\x6b\x6b\x0b\xb7\xe9\x5a\x7e\xb6\xe2\x16\xef\x9d\x97\xa4\x84\x59\x15\xbc\xfc\x0c\x44\x05\xc8\xff\xd2\x0f\x01\xee\xff\x41\x6e\x89\x48\x39\x5d\x48\xd1\xaf\xee\xc6\x75\x49\x1b\xff\x21\xea\xb5\x9a\xa1\x82\xd5\xbe\x68\x5b\xe9\xff\x4d\x06\xb9\x8e\x75\x8f\xa0\x73\x0f\x9b\x87\x51\xaf\x7e\xc7\x4d\x2e\x15\x8a\xab\x9b\xff\xc0\x59\x68\x9c\x00\x8f\x45\x19\xeb\x5d\x19\x48\xb4\x4d\x23\x4f\x2d\x2f\x9b\xc0\x1d\xb1\x27\xf2\x88\xc7\x44\x40\x82\xf0\x0c\xd6\x8d\x09\xfd\x4d\x44\x82\x7e\x6c\x90\xdf\x49\x78\xcb\x8b\xe5\x42\x57\xe3\xc7\xfe\xa4\xd2\x38\xd1\xdf\xa1\xfa\xe3\x44\xa4\x94\x76\x4d\xe4\x94\xc1\xfb\xe5\x7c\x0c\x5c\x74\x4d\x0b\x79\x97\x43\xd2\x58\x9d\xcb\xf5\x0b\x4c\x64\x82\x0e\x0f\xa3\xad\x14\x1f\xd5\x6e\x24\xe8\x30\x69\xd1\x08\xbd\x2f\x06\xe1\x7e\xcb\xb4\x65\x6f\xcf\xcf\x60\xbd\x4d\xfa\x0c\xd6\x96\xaf\x6b\xee\xfd\x32\xcf\x13\x74\x18\xb7\xe6\x58\xc1\x3e\x70\xca\x74\xa9\xd6\x49\x50\xea\xb4\x85\x7f\xe3\x3c\x6d\xf6\x39\x62\xad\xa3\xdf\x76\x26\xc8\xff\xbf\x0f\x94\x61\xaf\xbc\xc7\x61\x63\x5b\xca\x3e\x76\x3b\x3d\xf3\x1b\xb4\xad\xca\xd7\x63\x75\xd2\xd5\x06\x5b\xdd\x72\x8c\xac\xef\x09\x5b\x2e\xcf\xb8\x83\x45\x21\xaa\xd4\xc3\xb9\x6e\x9b\xa8\xcb\x19\x7f\x8b\x9b\xfc\x7f\xb9\xf4\xad\x41\x6b\x45\x38\xa3\x6c\xda\x88\x5b\x2a\x84\x22\x41\xbf\x00\x92\x45\x81\x72\xc2\xa7\xea\x17\xca\xa8\x58\xe4\xe4\x0e\x51\xa6\x8e\x7a\x8c\x74\x78\x53\x92\x55\x70\x7b\x4b\xe5\xbb\xe5\xd8\xc6\xaf\xad\x81\xc2\xef\x78\xea\x9e\xc2\xff\x05\x00\x00\xff\xff\x68\xc3\x30\x59\x52\x34\x00\x00") +var _staticJavascriptsApplicationJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x5b\xef\x72\xdb\x38\x92\xff\x9e\xa7\xc0\x22\x9e\x33\x19\x53\x94\xec\xbb\xcc\xce\xd0\x51\x72\x9e\xfc\xf5\xd5\x4c\x26\x95\x64\xee\xaa\xce\xf2\xfa\x20\xb2\x25\x21\xa6\x40\x15\x00\x59\x72\x62\x5d\xed\xd3\xec\x83\xcd\x93\x5c\x01\x04\x48\x80\xa4\x64\x39\xf7\x61\xa7\xb6\x1c\x11\xe8\xfe\x75\xa3\x01\xf4\x3f\x72\x6f\x08\x47\x9f\x24\x91\x02\x0d\xd1\x2f\x24\xbd\x1e\x17\x0c\xe2\xdf\x8a\x0c\xf2\x18\xd6\x12\x58\x16\x7c\x7b\x84\xd0\x92\xe7\x09\xc2\x7d\xa1\x08\x71\xf4\x08\xa1\x0c\x26\x64\x99\x4b\x91\x20\x35\x8d\x10\x56\x18\x4b\x81\x13\x64\xfe\xc3\x94\x51\x49\x49\x4e\xbf\x52\x36\xd5\x2c\x25\x11\x97\x90\x9d\x49\x43\xc7\x96\x79\x6e\xa6\xde\x50\x46\xc5\xac\x9e\x73\xa6\x3e\xf0\x62\xca\x41\x54\xe0\x03\x33\xfe\x99\xf0\x29\xc8\x5a\xa6\x1d\xff\x08\x8b\x42\x50\x59\x70\x0a\x7a\xd2\x8e\xbf\x2c\xe6\x73\xda\x41\xff\x86\xe6\xe0\x68\xee\x8c\xb3\x8c\xb2\xa9\x2f\x77\xa3\xfe\x50\x61\xd5\x4d\xd0\x64\xc9\x52\x49\x0b\x16\x84\xc6\x14\x1c\xe4\x92\x33\x24\x67\x54\xc4\x53\x90\x81\x35\x4d\x88\x86\xc3\x21\xc2\x13\xc3\x89\x4f\x2d\x5a\xb6\xe4\x44\x21\x74\x60\xd1\x09\x0a\x3c\x20\x63\xbe\x12\x4b\xd9\xc8\x52\x56\x72\xf1\x60\x90\xe8\xff\x69\x01\x08\x6d\xf4\x5f\xb5\xcd\xc0\xb2\xd3\xea\x41\x28\x2c\x34\x44\xaf\x88\x84\x78\x41\xb8\x80\x6e\x41\xe1\xa9\xaf\x48\xbd\xf4\x20\xac\x65\x03\xcb\xb6\x61\x39\x1b\x6b\xc1\x36\x08\x72\x01\x5d\xcc\xac\x58\x05\x61\x53\xef\x39\xcd\x73\x2a\x10\x42\x43\x4d\xda\x2b\x75\x77\x96\x02\x69\xc1\x32\xa1\xe6\x7f\x23\x72\x16\x4f\xf2\xa2\xe0\x81\xe1\xea\xa3\xe3\xc1\x60\x10\xd6\xd4\xca\x68\x4a\x16\x1a\x22\x06\x2b\x2d\x36\xd0\x86\x2c\x49\xec\x74\x2c\x40\x7e\x2a\x81\x03\x23\xc0\x50\x18\x3b\x57\x84\xb2\x38\xff\xf4\xfb\x27\xc9\x29\x9b\x06\x61\x2c\x96\x63\x21\x79\x70\x7c\x1c\xa1\x9f\x42\xb3\xc5\x9b\xf0\xf4\xd1\x8a\xb2\xac\x58\xc5\xc2\x5c\x35\x25\x5a\x5f\xbb\xd3\x47\x8f\x94\x56\xe6\xac\xed\xbc\x84\x34\x3b\x93\x92\xd3\xf1\x52\x42\x82\xf0\x79\xa6\x6f\x95\x04\x21\xd5\x01\x3e\x67\x19\x4d\x89\x2c\xb8\x48\xd0\x05\x56\xa3\x38\x42\xf8\x4a\x2c\x20\x55\x3f\x26\x74\x2d\x97\x1c\xd4\xcf\x79\x91\x5e\xab\x7f\x85\x5c\x8e\xf5\x14\xb9\xd6\xe3\x19\xcc\x0b\x3d\x4e\xe6\x8b\x1c\xf0\xa5\x42\x17\xb3\x82\xcb\xf2\xde\xbc\x23\x62\xb6\xcf\x69\xaf\xa9\x71\x65\x8d\x41\x84\xfe\x1a\x56\xe7\x5d\x72\x3a\x9f\x43\x56\x12\xfe\x06\x42\x90\x29\x74\x20\xeb\xad\x2f\x67\xd1\xb0\x25\xc0\xf0\x29\x19\x8b\x9c\xca\x00\xf7\xd4\x7f\xaf\xdf\xbf\x42\x1f\xde\x7e\x40\x9f\xce\xdf\xbe\x3f\xfb\xfc\xc7\xc7\xd7\x7a\x14\x47\xe8\x24\x8c\x17\xc5\x22\xf0\xb7\xd0\xa0\xc7\x1c\x16\x39\x49\x21\xe8\xff\x6d\x24\x46\xe2\x49\x3f\x42\x18\x87\xf5\xa8\x1e\x3c\x28\x47\x6b\x0f\xf0\x19\x84\xfc\x08\x39\x91\x9d\x4e\x40\x29\xbf\x20\x72\xe6\x69\xae\xf6\xe9\x03\x91\xca\x30\xb2\xf8\xb5\x58\x01\x7f\x49\x04\x58\xa5\x26\x05\x47\x81\xe2\xa3\x68\x88\x06\xa7\x88\xa2\x67\x25\x6f\x7b\x8b\xe3\x1c\xd8\x54\xce\x4e\x11\x3d\x3a\xaa\x2f\xa1\xba\xa3\x4a\x66\x4c\x59\x06\xeb\xdf\x27\xc1\x16\xee\x0b\x7a\x19\xa2\xe7\xa8\x77\x5c\xb3\xd6\xfb\xc8\x97\x70\x6a\x06\x37\xce\x3d\x34\xd3\x13\x92\x0b\xa8\x36\x72\x42\x73\x78\x59\x30\x09\x4c\x8a\x3f\x54\x84\xd8\x76\x3a\x2e\x70\x7f\xa2\x9d\x6c\xe4\x58\xa3\x72\xd3\xb7\xbf\xaf\x18\x70\x1c\x76\x4f\xbe\x27\x73\xf0\xe7\xdc\x13\x16\x75\x9a\xf7\x32\xfe\x52\x50\x16\xe0\x3e\x0e\x3b\x95\x75\x34\x4d\x49\x9e\x8f\x49\x7a\x1d\x21\xe0\xbc\xe0\x56\xf1\x83\x98\x7c\x21\xeb\xc0\xda\x47\xc7\x3f\x2d\xa9\xb1\xe6\x20\x8c\x0c\x89\x58\xa6\x29\x08\x91\xa0\x0a\xd1\xba\x37\x85\x9b\x94\xff\x94\x16\x75\xfd\x82\x7b\xfb\xbd\x18\xfc\xb2\xc8\x73\xd0\x3a\x76\x04\xe2\x89\x0d\x4d\x4a\xc8\x5c\x39\x8a\xc4\x82\x18\x58\xe3\x6f\x26\x35\xb2\x72\x39\x56\x50\x60\x25\x6b\x1f\xf4\x9f\x14\x56\xae\x68\xf5\xec\x3b\x9e\x44\xb9\x0b\x22\xc5\x55\x5a\x30\x49\xa8\xda\x2e\x47\xb2\x9e\x52\xcf\x8b\x22\xcf\x29\x9b\x7e\xa6\xe9\x35\xf0\xa4\x8a\xe1\x36\xc0\x35\xc7\x0d\xf9\x39\x93\xc0\x6f\x48\x9e\xa0\xa7\x03\x1d\x63\xab\xd4\xa1\xcb\x2d\xe8\x5d\xc8\xa9\x90\xc0\x3e\x17\xe5\x11\xd7\x6a\x44\x08\xa7\x33\xc2\xa6\x60\x0f\x19\x07\x96\x01\x0f\x6b\x26\x1d\x36\x5e\x79\xba\xd8\xbb\x57\xcf\x7f\x28\x75\x0a\xea\x83\x53\xe2\xec\x0a\xce\x5a\xfe\x96\xc8\x68\x90\x8b\x85\x07\xec\xcd\x74\xab\xb4\xe9\x92\x31\x23\xe2\xa5\x5e\x64\x16\xd4\x69\x51\x53\xda\x72\x91\x11\x09\x76\x7a\x6f\xbc\x2a\xdd\xe9\xc6\x73\x8f\xce\x9e\x78\xea\xc6\x6f\x03\xcb\x61\x7f\x24\x9b\xb8\x75\x63\x99\xd9\xbd\xd1\xbc\xf4\xb0\x1b\xd2\x25\xd9\x1b\xd7\xa6\xa3\xdd\x90\x66\xd6\x45\xd3\xa7\xcb\x3d\x74\xdb\x4e\xbb\x77\xad\xd0\x10\x09\x90\xf6\xce\x04\x2d\x0e\x54\x5e\x47\x7d\x87\x4b\x25\x27\x20\xd3\x59\x25\x38\xf2\x30\x2d\x4e\x7d\xdc\x9d\xb3\xba\xeb\xcc\xfb\x3a\xfd\xa5\x95\x8d\xa6\x39\x10\x5e\x69\xd9\x66\xe9\xb4\xc3\xab\x86\xa3\xe8\x36\x87\x4f\xf5\x10\x7b\x94\x5b\x61\xf9\x83\xd0\x5a\xa4\x4a\x11\x2b\x0b\xec\xa7\x49\x13\xaf\x91\x2b\xfb\x7e\x6f\x3f\x23\xf9\x3c\x4d\x2b\xf9\x02\x3b\xd4\x3a\x08\xf0\xe3\x94\xf0\xec\xca\xe2\x5c\xdd\x90\x7c\xa9\xd2\x24\x09\x6b\xe9\x1e\xdd\xac\xd2\xba\x5e\xb9\xef\x39\xb6\xa4\x35\x42\x97\x33\x36\xb1\x29\x9f\x3e\x17\xef\x96\x73\x52\x59\xe0\x20\xc0\x92\xca\xbc\x12\x8b\xdf\x52\xc9\x8b\x71\x82\x30\x3a\x32\xfc\x35\xe5\xe3\x85\x91\x77\x35\x26\xdc\x72\x18\xa2\x38\x15\x22\xc0\x2b\x9a\xc9\x99\x75\xeb\xa5\xf6\x3a\xe0\xd7\x1e\x10\x1d\x21\xfc\x03\x6e\xda\x7f\x97\x5f\xee\x10\xcc\x61\x5e\xdc\xc0\xcb\x9c\x28\x99\x76\xae\x37\x26\xbc\x47\x18\x9d\xab\x44\x0f\x79\xa3\x42\x72\xba\x80\x0c\x37\xb4\xc4\xc7\x83\x41\xa5\x4b\x63\xe7\xac\x13\xdd\xb5\x73\x36\x64\x57\x3b\x37\xa3\x19\x04\xed\x0d\xb4\xd5\x95\x71\xda\x3a\xa5\x4c\x49\x0e\xb6\x14\x09\xe3\x09\xc9\xe0\x9c\x05\x78\x42\x84\xc4\xcd\x5d\xd6\x2e\x78\xb7\x1e\x39\xec\xab\x84\xf6\xf4\x0f\xd5\xc0\x38\xee\x5d\x3a\xa4\x25\xc9\x5e\x5a\x54\x51\xe2\xa1\x7a\xb8\xde\x7e\x97\x32\xdc\xa1\xdb\x4b\x23\x3f\xd2\x3c\x54\x2d\x13\x31\x76\x69\x24\x4b\x92\xbd\x94\xa9\xc2\xd3\xfe\x7a\x78\x77\x7b\xa7\x37\x28\x0f\xbb\x58\x51\x15\x69\x9a\x92\x6d\xff\xc3\xb2\xa5\x44\x40\xa3\x3f\x94\x38\xae\x5a\xfb\x16\x7c\xee\x4e\xdb\x94\x69\xcc\x81\x5c\x9f\x3a\x20\x53\x22\x67\xc0\xbb\x11\xde\xda\x39\xe4\x6e\xdc\x76\x2c\xc2\x48\x7e\xbb\x45\x9b\x33\x3b\xe7\x63\x6d\x83\xaa\x7a\x3c\x6d\xa4\x37\x6e\xfb\xa7\xc1\x6c\x9a\x6a\x6d\xa6\x3f\xd8\x35\x2b\x56\xac\x8b\xc7\x2b\xcf\x0c\xc7\x11\xc2\x28\x50\xae\x56\xf7\x62\xce\x59\xfb\x30\xb8\xb9\xa3\x72\x9d\x61\xd9\x8d\x6a\x75\x2a\x4c\x65\x50\x75\x2b\xd4\x73\xf0\x4d\xe5\xfc\xea\x0c\x36\x4b\x82\xb0\x59\xd0\xdc\x57\x58\x48\x32\x55\xe5\x5d\x82\xb0\x2c\x0b\x0a\xb8\x29\xcb\x33\xd3\x54\x4c\x73\x9a\x5e\x23\x99\xc5\x69\x91\xf7\x74\x39\x4d\xb0\x2a\x45\x66\xc5\xca\x48\xc0\x55\x4f\x4e\xc2\x7c\xa1\xaa\xf1\x04\x5d\xc5\xf6\x77\xa0\xb4\xb4\x0f\xd6\xb1\xaa\x7b\x22\xe7\x79\x10\x86\x3b\xb3\x7b\x6d\xb2\x03\x95\xe3\x29\x62\x53\x4a\x1b\x58\xc7\x9c\xc4\xb6\x64\x44\x18\xc6\x19\x91\x24\xc0\x56\x8e\x1b\xb0\xb6\x85\x26\xa7\x8b\xd0\xaa\x1a\x94\x70\x92\x65\x26\x20\xa9\x3a\xbe\xc7\x4b\x52\x1c\x76\x6c\xbe\xe2\xa9\xab\xdd\x82\xcf\x89\x94\x90\xd9\xa2\x78\xdb\xf5\x5d\xe4\x54\x0a\xa4\x9b\x6b\x5d\x6e\xdd\xb4\x2b\x4c\x8f\xa5\x8f\x9d\x46\x9a\x8a\x0f\x8c\xcc\x41\xb1\x96\x30\x6e\x8b\x45\x51\x64\x94\x43\xaa\x6a\x78\x0b\x0e\x79\x4e\x17\x82\x0a\xfa\x15\x02\xc3\x52\x15\xea\x11\xfa\x71\x10\xa1\x93\xa7\x8e\xa5\x1c\xfe\xe1\x10\x61\xdc\xee\x75\x3e\x13\x92\x17\x6c\xfa\x5c\x1d\xf6\xab\x18\x44\x4a\x16\x10\x58\xc5\xf4\xd1\x7e\xd6\xb7\x24\x1d\x26\xab\x58\x2a\x49\x9a\xa7\x8f\x35\xe7\x03\xb1\xb5\xdd\x9d\x15\x3a\x16\x17\x92\x47\x68\x4e\xd9\xaf\xba\x6d\x13\x21\xc8\xa6\x50\xfe\xb6\x4b\x12\x52\x25\xb2\xc6\x23\x0b\xc9\x1d\x2b\x08\xc9\x4d\xbf\x07\x3d\xab\x41\xd0\xdd\x1d\x72\x67\x86\x28\xa8\x51\xd1\x13\x74\x12\xb6\xac\x25\x24\x6f\xb5\x84\xb3\x29\x94\x34\x43\x74\xc6\x39\xb9\x75\x41\x8e\xd0\x71\x68\xf6\x27\x76\x37\x7e\x4e\x33\x43\x31\x74\x55\xe8\x21\x5f\x81\x53\xb7\x11\x26\x81\x33\x2d\x05\x6b\xc7\xa4\xe5\x1e\x21\x1c\xc6\xdf\xd4\x63\x8d\x78\x84\xf0\xc6\xa7\xc0\xa7\xbe\x87\xe3\x55\x63\x4e\x79\xa5\x8f\x30\x7d\xbd\x5e\x04\x46\x42\x18\x21\x7c\x70\xfc\xe7\xdf\xff\x71\x70\xe2\x86\xb1\xda\x5d\x38\x7b\x02\xd6\x3e\x10\x2f\xb8\xf6\x3b\xaf\x4a\xf7\xeb\xf5\x04\xe6\x84\x5f\x9f\x89\x4f\x90\x43\xaa\xaf\xa8\x63\x85\x22\x23\xb9\xe3\x1f\x8d\x84\xdf\xd4\x70\xd5\x37\x32\x0d\x12\xa7\x4b\x61\x9b\x42\x79\x82\xf0\x63\xe3\x29\xae\x34\x16\x8a\xf5\x3f\xbd\xb4\xec\x2e\x61\xa7\x57\x84\x6a\x69\xa6\xad\xe1\x64\xda\x3e\x0a\x0e\x4b\x98\xa0\xc5\xa8\xcb\xc0\x37\x4e\xfb\xca\xe9\x71\xf8\xcb\xdc\xe5\x0d\xd3\xbc\x10\x20\x64\x80\xe5\xb8\xc8\x6e\x71\xa8\x3b\x4c\x01\x96\x3c\x96\x64\x9c\x43\x4f\x18\x8c\x66\x3e\xdd\x9c\x3d\x7d\xb4\xcd\xcf\x75\x10\x76\xf5\xca\xee\x8b\x2d\x69\xd5\x3f\x4b\x90\x4d\xa9\xbf\xa7\xb9\x54\xe3\x44\x08\x93\x2c\xf3\xdb\x4b\x46\x1b\x77\x39\x15\x7b\xd9\x16\xb3\x6d\xa9\xa4\x4a\xd5\x23\x74\x15\x67\x30\x2e\x96\x2c\x35\xa1\xa4\x4c\xf8\x22\xf4\x74\x30\x08\xdb\x1b\x2b\xae\x04\x10\x9e\x2a\x3f\x5c\xb0\x00\x5f\xc3\xed\x72\xd1\x01\x52\x12\x59\x29\x11\x3a\xe9\x04\xab\x4e\x89\x82\x52\x37\x23\x1e\x8b\xf2\xc4\xe0\xc8\xb9\x1c\xea\x3e\xb8\xc5\x52\x56\xa4\xcb\xb9\x1a\xb3\x2a\x64\x2a\x1f\x89\x3a\xae\x93\x93\x08\x42\x7c\x0d\xb7\x2f\x8b\xcc\x9b\xd3\x19\xd2\xbf\xfe\x35\xa9\x06\x6c\x34\xb1\x2f\x40\x26\xce\x06\xeb\xab\x49\x8b\xa5\x30\xcb\xaa\xbb\x67\x8d\x34\xa8\x46\xfe\x79\x4f\x64\x06\x6b\xb9\x0f\x6a\x23\x29\xab\x7d\x51\x4d\xb2\xa9\x7e\x29\x7f\x6d\xa4\x58\xb7\xa8\x42\xd7\xc0\x35\xc0\x2e\x7e\x4f\x43\x92\x4a\x7a\x03\x95\x8e\xfb\xdc\x27\x07\xe3\x9e\x2b\x55\xdb\x67\x2f\x47\xe6\x38\x33\x8b\xef\x27\x3b\x55\x1f\xfc\x21\xde\xcd\xf5\x70\xbb\xbc\xdc\xb6\x33\xec\x79\x3a\xb4\x8f\xb7\x73\x25\x6e\xca\xfe\x8f\x3e\xd1\x33\x9a\x65\xc0\x1e\x7a\x17\x96\x6c\xac\xbd\x9f\xbd\x0f\x15\x70\xa3\x94\xdb\xe6\x69\x6a\xdf\xe2\x36\xe9\x9c\xae\x73\x3b\x6c\x19\x13\xb8\x39\x9c\x19\x7a\x9d\xfb\x1b\x58\xe6\xea\xfe\xa6\x6d\xc2\xca\xb2\x31\xe4\xd6\x39\x54\x00\x61\x4c\x16\x0b\x60\x99\xf5\x7d\x07\xe0\x34\x06\xbd\xe3\x78\xcf\x9b\x40\xe5\xd2\xb7\x06\x86\x0a\xd1\xb9\x82\xf7\xe0\x35\xaf\x82\xe2\x3c\xcb\x73\x05\x8f\xc3\x98\x15\x32\xc0\x71\xd6\x63\x05\x03\x1d\x91\xb8\x90\x8e\x29\x1b\x3e\xe4\x81\xa2\x14\xf7\xde\xa2\x7c\x1f\xbc\x25\xe7\x66\x00\x59\x0e\x68\x88\x0e\x62\xc9\xe9\x3c\xe8\x76\xf5\x37\xea\x64\x77\xbe\x28\x54\x4e\xc6\x62\xf8\x69\xb1\x2e\x77\x94\xb5\xab\x26\x12\xd2\x61\x1a\xc9\x56\x7f\xcb\x2e\xe1\xf4\x51\xdb\x27\x6d\x1e\xdd\x0f\x06\x24\x9d\x75\x35\x5b\x9d\x17\x9f\x07\xfa\x10\x55\x19\x42\x5d\xc5\xd9\x26\x5f\xe7\xea\x4a\x88\xb2\xf1\xb3\x0d\xa4\x9c\xdd\x03\xa6\xaa\xd6\x6f\xb7\x41\xd5\x14\xf7\xc0\xb5\xde\xae\x96\x5b\x50\xbe\x49\x55\xd9\x78\xa9\xd4\xd6\xe9\x5a\x50\x27\x89\x1b\x21\xac\xa2\x3b\x37\xac\xf1\x11\x45\xcd\x55\xfb\xfd\x16\x8b\xeb\x9d\xdc\x5a\x7f\xe2\xe7\x54\xee\x9b\xc2\xba\xe2\xef\x3e\x0d\xb8\x99\x98\xe9\xb8\xb1\xb3\xe8\xdf\xb7\x50\xaf\xdc\xbc\x53\xae\x53\x26\x81\x83\x90\x94\x4d\xcb\x62\xe9\x43\x99\xf9\x8b\x04\x5d\xe8\xd5\xf5\x83\xe0\xe4\xe9\xc5\xa0\xf7\xf4\xf2\xee\xe4\x62\xd0\xfb\xb7\xcb\x8b\x41\xef\xe7\xcb\xbb\x8b\xc1\xf1\xe5\x0b\xfd\x53\xff\x79\x11\x8e\xe2\x7f\x0e\x5d\xd8\x9f\xce\x69\x64\x54\xbd\x20\xbd\xaf\x67\xbd\xff\x1e\xf4\x7e\x8e\xff\xf2\xf8\xe0\x87\x7f\x79\x72\xd4\x1f\xbe\xf8\xdb\xd5\xff\x7c\xbb\xdb\xfc\x6f\xef\xf2\xe8\xdf\xeb\xf9\xcb\xe0\x45\x52\x3f\xf5\x2e\xbf\x0d\xa2\x1f\x8f\x37\xce\x7c\xf8\x22\x78\x91\x8c\xe2\x07\x71\x84\x4f\x3c\x6d\x82\xd1\xea\x49\x32\xea\x8f\xfa\x61\x70\x31\xca\x48\xef\xeb\x28\xee\x5d\x1e\xa9\x95\x5d\xe8\x87\xcb\x6f\x27\xd1\x8f\x9b\xd6\x0a\x26\x83\xde\xcf\xa3\xde\xe8\x60\xd4\xbf\xfc\x76\x32\x88\x36\xde\xfc\x52\x00\xd7\xf5\xb2\x3b\x28\x20\xe5\x20\xbd\xa1\x05\x11\x62\x15\x14\x3c\x7c\x91\x79\xe3\x29\x87\x2c\x10\x77\xc0\x54\xce\xee\x8b\x26\xfa\x7d\x7b\x70\x75\xd7\xbb\x8b\xc3\x17\xb2\xb8\x06\x56\xcd\x5f\x6e\x6d\x26\x55\x39\xc4\x0d\x85\xd5\x15\x27\x2b\xdb\x50\xfa\x48\x56\x36\x55\xb0\x9f\xab\x75\x71\xcc\x60\x9d\x2d\xe7\x0b\xcb\xf5\x0e\xd6\xaf\x96\xf3\x85\xc7\xb9\xfb\xad\xf1\x77\xf4\x95\xcc\x97\x49\xb0\x42\x2f\x73\xba\x18\x17\x84\x67\xff\xf1\x29\x38\x8c\xc7\x92\x1d\x46\xf5\xcb\x24\xdb\x87\x4b\x90\xcd\x50\xe2\x29\xc8\xd7\x39\xa8\x9f\xbf\xdc\x9e\x67\xc1\xa1\x77\xb3\x0e\x43\xaf\xc4\xec\x6a\x23\x35\x0c\xb3\xa5\x17\xdd\x32\xa9\xeb\x84\xca\x78\x8a\x3b\x2a\x11\xcf\x9e\x0d\x6f\xd7\xe6\xd2\x2a\xeb\x97\x12\x0e\x4f\xd9\xf0\xee\x24\x4a\xed\x96\x84\xb1\x5a\x45\xe0\xf7\x03\x1a\xfb\xb6\xff\xc2\xee\xd1\x72\xcb\xda\x76\x99\xa3\x5b\xe7\x1d\x2b\xab\x61\x1b\x0b\x9b\x82\x7c\x57\x08\x59\xb6\x54\xef\xfb\x5c\xc1\x79\x67\xf2\x07\x57\x5e\xd6\x46\x25\x3c\xa5\x72\xb6\x1c\xe3\x50\xbf\xb0\x54\x91\xc9\x76\xdb\xde\x96\x13\xde\x71\x51\x83\xbf\x92\x31\x76\xbe\xee\x5a\xb2\x94\xc8\xef\xfa\xbe\xab\xd4\xac\xeb\xfb\x30\x37\xf7\xb1\x9f\x6d\xd5\xad\xaf\xe3\xa7\x83\x56\xb7\xab\x6a\xd9\x19\xf2\xae\x96\x69\x93\xc6\xf9\x5e\x4d\x41\xea\x26\xdf\x9f\x7f\xff\x47\xbd\xb8\xfb\x3e\xfb\x72\x13\xc9\xce\x16\xaf\x83\xf4\x0b\x65\x84\xdf\x3a\x20\xaa\xa0\x6a\x00\xf5\x2f\x46\xeb\xc1\xa0\x37\x5a\x0f\x7e\x1a\xad\x07\xaf\x7b\xa3\xf5\xf1\x9b\xcb\xbe\xfe\xa6\xab\x24\xaf\xf0\x66\x74\x3a\xcb\xe9\x74\x56\xbe\x09\x77\x23\xa4\x7b\xb8\x67\xe4\x56\x48\x92\x5e\x7b\xce\x68\x6b\x4c\x8d\x27\x05\x7f\xed\xe5\x79\xb6\xcf\x56\x19\xdb\x02\xa2\x61\xf5\xb3\xea\xcf\x19\xe2\x08\xe1\x67\x73\xc2\xaf\x9f\x1f\x1c\x3f\xeb\xeb\x1f\x7e\x9d\x54\x2d\xd6\x02\xd4\x4d\xec\x66\x0d\xb7\xe7\xa9\x3e\xd3\x24\xfa\x23\x59\x84\x5f\x41\x0e\x12\xb0\x9f\xa1\x9a\x8b\x86\x86\x08\x3f\xcb\xe8\x0d\x4a\xd5\xe5\x1c\x1e\x92\x1c\xb8\x44\xfa\x6f\x8f\xb2\x49\x71\x88\x78\x91\x83\x19\x3f\x7c\xae\xd3\x23\x93\x99\x16\x0c\xfd\x20\x90\x2c\x90\x00\xb0\x70\x02\x15\x13\x94\x69\x79\x99\x6e\x8f\x8b\xf8\x59\x3f\xa3\x37\xcf\xb1\x9b\x93\xce\x0a\x21\x9d\xcf\x02\xed\x8d\xf5\x13\xd7\xf2\x8d\xdc\x9b\x25\x4b\xd1\xb0\xbd\xe8\x1d\xae\xc3\x7d\x1d\x53\x86\x18\x33\x53\x6d\x0b\xfe\x41\xe0\x48\x6b\xd1\xf9\xe6\xcf\xa2\x1f\xfa\x05\x3b\x7a\xac\x5c\x5a\x4f\xc9\x8a\xda\x41\xb4\x39\x64\x5c\xd4\xa1\xe3\xf9\x0e\x33\x2a\x54\xfe\x98\x1d\xaa\x84\xd1\x4d\x48\x5b\x6b\x11\x0b\xca\x18\x70\x6f\x29\x4a\xd1\xdf\x97\xd2\x68\x1a\x39\x06\x0a\xc2\x1d\x45\x4c\xb5\xd9\x6b\x6b\x73\xb7\xb9\x5b\x7e\x1e\xe3\x36\x09\x3a\x2f\x63\x09\xb3\x2a\x78\xf9\xb9\x89\x0a\xc4\xff\xa5\x1f\x02\xdc\xff\x42\x6e\x88\x48\x39\x5d\x48\xd1\xaf\xee\xe0\x55\x49\x1b\x7f\x11\xb5\x4d\xcd\x50\xc1\x6a\x9f\xb7\xad\xc5\xf0\x5d\x06\xb9\x8a\x75\x2f\xa2\xf3\xac\x34\x0f\xbd\x5e\xfd\x0e\x8f\x51\x2a\x14\x57\x1e\xe6\x9e\x33\xd7\x38\x69\x1e\x8b\x32\xd6\xbb\xf2\x34\x68\x9b\x46\x9e\x5a\x5e\xd6\x82\x3b\x62\x5c\xe4\x11\x8f\x89\x80\x04\xe1\x19\xac\x1b\x13\xfa\xdb\x8b\x04\xfd\xd4\x20\xbf\x95\xf0\x96\x17\xcb\x85\xae\xfa\x8f\xfd\x49\xa5\x71\xa2\xbf\x77\xf5\xc7\x89\x48\x29\xed\x9a\xc8\x29\x83\xf7\xcb\xf9\x18\xb8\xe8\x9a\x16\xf2\x36\x87\xa4\xb1\x3a\x97\xeb\x57\x98\xc8\x04\x1d\x1e\x46\x5b\x29\x3e\xaa\xdd\x48\xd0\x61\xd2\xa2\x11\x7a\x5f\x0c\xc2\xdd\x96\x69\xcb\xde\x9e\x9f\xc1\x7a\x9b\xf4\x19\xac\x2d\x5f\xd7\xdc\xfb\x65\x9e\x27\xe8\x30\x6e\xcd\xb1\x82\x7d\xe0\x94\xe9\x92\xb0\x93\xa0\xd4\x69\x0b\xff\xc6\x79\xda\xec\x73\xc4\x5a\x47\xbf\xcb\x69\x79\xff\x37\x85\x32\xbc\x96\xf7\x38\x6c\x6c\x4b\xd9\x2f\x6f\xa7\x81\x7e\x23\xb8\x55\x61\x7b\xac\x4e\x5a\xdc\x60\xab\x5b\x9b\x91\xf5\x3d\x61\xa3\x06\xaf\xdc\xc1\xa2\x10\x55\x8a\xe3\x5c\xb7\x4d\xd4\xe5\xf4\xbf\xc7\x4d\xfe\xbf\x42\xc7\xd6\xe0\xb8\x22\x9c\x51\x36\x6d\xc4\x47\x15\xaa\x91\xa0\x5f\x01\xc9\xa2\x40\x39\xe1\x53\xf5\x0b\x65\x54\x2c\x72\x72\x8b\x28\x53\x47\x3d\x46\x3a\x8c\x2a\xc9\x2a\x88\xbe\xa5\xf2\xdd\x72\x6c\xe3\xe4\xb6\xbd\xdd\xf8\x9d\x55\xdd\xbb\xf8\xbf\x00\x00\x00\xff\xff\xdd\x8d\xff\x65\xba\x34\x00\x00") func staticJavascriptsApplicationJsBytes() ([]byte, error) { return bindataRead( @@ -296,7 +296,7 @@ func staticJavascriptsApplicationJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/javascripts/application.js", size: 13394, mode: os.FileMode(420), modTime: time.Unix(1583789200, 0)} + info := bindataFileInfo{name: "static/javascripts/application.js", size: 13498, mode: os.FileMode(420), modTime: time.Unix(1583790916, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/static/index.html b/static/index.html index 612cd4af..5808c171 100644 --- a/static/index.html +++ b/static/index.html @@ -180,8 +180,8 @@ diff --git a/static/javascripts/application.js b/static/javascripts/application.js index 507a0012..d8dc0071 100644 --- a/static/javascripts/application.js +++ b/static/javascripts/application.js @@ -364,6 +364,7 @@ var FindingModal = Backbone.View.extend({ var host = this.getHostName(); var fadeInFunc = function() { $("#modal_file_contents_container").html(content.replace("%s", host)).fadeIn("fast"); + $('.modal-content #view-file, #finding_view_raw, #finding_view_hexdump').addClass('disabled'); } $("#modal_file_spinner_container").fadeOut("fast", fadeInFunc()); return; From 54f701e133b0613b3627f29bb2ca9f16aa69ed1f Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 9 Mar 2020 18:03:05 -0400 Subject: [PATCH 050/226] version bump --- core/banner.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/banner.go b/core/banner.go index 2e727c43..b8b4ec24 100644 --- a/core/banner.go +++ b/core/banner.go @@ -1,13 +1,13 @@ package core const ( - Name = "gitrob" - Version = "2.0.0-beta" - Author = "Michael Henriksen" - Website = "https://github.com/michenriksen/gitrob" - ASCIIBanner = " _ __ __\n" + - " ___ _(_) /________ / /\n" + - " / _ `/ / __/ __/ _ \\/ _ \\\n" + - " \\_, /_/\\__/_/ \\___/_.__/\n" + - "/___/ by @michenriksen" + Name = "gitrob" + Version = "2.1.0-beta" + Author = "Michael Henriksen" + Website = "https://github.com/michenriksen/gitrob" + ASCIIBanner = " _ __ __\n" + + " ___ _(_) /________ / /\n" + + " / _ `/ / __/ __/ _ \\/ _ \\\n" + + " \\_, /_/\\__/_/ \\___/_.__/\n" + + "/___/ by @michenriksen" ) From ef986b9b32376e70f342b9edb1ce902fee42b629 Mon Sep 17 00:00:00 2001 From: Chris Moberly Date: Tue, 10 Mar 2020 11:35:20 +1100 Subject: [PATCH 051/226] added GCP credentials signatures --- core/signatures.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/core/signatures.go b/core/signatures.go index d5251f73..b453b3b1 100644 --- a/core/signatures.go +++ b/core/signatures.go @@ -713,4 +713,34 @@ var Signatures = []Signature{ description: "Contains word: password", comment: "", }, + SimpleSignature{ + part: PartFilename, + match: "credentials.json", + description: "Google Cloud Platform service account credentials keyfile", + comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", + }, + PatternSignature{ + part: PartFilename, + match: regexp.MustCompile(`^.*-[a-f0-9]{12}\.json$`), + description: "Google Cloud Platform service account credentials keyfile", + comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", + }, + SimpleSignature{ + part: PartFilename, + match: "adc.json", + description: "Legacy Google Cloud Platform service account credentials keyfile", + comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", + }, + SimpleSignature{ + part: PartFilename, + match: "credentials.db", + description: "Google Cloud Platform gcloud credential database", + comment: "sqlite database containing credentials used by the gcloud command from Google's Cloud SDK", + }, + SimpleSignature{ + part: PartFilename, + match: ".boto", + description: "Legacy Google Cloud Platform gcloud credential database", + comment: "File containing credentials used by the gcloud command from Google's Cloud SDK", + }, } From 644a3061f9ac7f88650cf2413163b6eb9965ea3e Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 9 Mar 2020 20:50:29 -0400 Subject: [PATCH 052/226] add 50% to max file size --- core/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/router.go b/core/router.go index f0dce32a..a6dab2c6 100644 --- a/core/router.go +++ b/core/router.go @@ -14,7 +14,7 @@ import ( const ( GithubBaseUri = "https://raw.githubusercontent.com" - MaximumFileSize = 102400 + MaximumFileSize = 153600 GitLabBaseUri = "https://gitlab.com" CspPolicy = "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'" ReferrerPolicy = "no-referrer" From ad471b2741dbd8e0f6152188c8f01c958e9e4f95 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 10 Mar 2020 12:35:43 -0400 Subject: [PATCH 053/226] correct missing param error --- main.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 01b72ab3..6d02940b 100644 --- a/main.go +++ b/main.go @@ -262,7 +262,14 @@ func main() { sess.Out.Important("Loaded session file: %s\n", *sess.Options.Load) } else { if len(sess.Options.Logins) == 0 { - sess.Out.Fatal("Please provide at least one GitHub organization or user\n") + host := func() string { + if sess.Github.AccessToken != "" { + return "Github organization" + } else { + return "GitLab group" + } + }() + sess.Out.Fatal(fmt.Sprintf("Please provide at least one %s or user\n", host)) } GatherTargets(sess) From 649de660c72592235e95f087ce73fdf152a2fbd7 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 10 Mar 2020 13:54:50 -0400 Subject: [PATCH 054/226] new line following common error --- core/session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/session.go b/core/session.go index 298091ea..d36f855b 100644 --- a/core/session.go +++ b/core/session.go @@ -150,7 +150,7 @@ func (s *Session) ValidateTokenConfig() { s.Out.Fatal("Both a GitLab and Github token are present. Only one may be set.") } if s.GitLab.AccessToken == "" && s.Github.AccessToken == "" { - s.Out.Fatal("No valid API token was found.") + s.Out.Fatal("No valid API token was found.\n") } } From bad7cef9c5a964231dc0c0ae0e647bb3dffa7ca4 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 10 Mar 2020 17:34:30 -0400 Subject: [PATCH 055/226] simplify error handling --- gitlab/targets.go | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/gitlab/targets.go b/gitlab/targets.go index 5a826b4c..8d9956b0 100644 --- a/gitlab/targets.go +++ b/gitlab/targets.go @@ -9,27 +9,16 @@ import ( "github.com/xanzy/go-gitlab" ) -type errorString struct { - s string -} - -func (e *errorString) Error() string { - return e.s -} -func new(text string) error { - return &errorString{text} -} - func getUser(login string, client *gitlab.Client) (*gitlab.User, error) { users, _, err := client.Users.ListUsers(&gitlab.ListUsersOptions{Username: gitlab.String(login)}) if err != nil { return nil, err } if len(users) == 0 { - return nil, new(fmt.Sprintf("No GitLab %s or %s %s was found.", + return nil, fmt.Errorf("No GitLab %s or %s %s was found.", strings.ToLower(common.TargetTypeUser), strings.ToLower(common.TargetTypeOrganization), - login)) + login) } return users[0], err } From 6ffb75045b3e4ddaa2944320e3a95b6cd5d05a23 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Wed, 11 Mar 2020 15:10:39 -0400 Subject: [PATCH 056/226] rename targets file for clarity. begin isolating api client implementation detail. --- common/browser.go | 5 + common/interfaces.go | 7 ++ github/{targets.go => apiClient.go} | 16 +++ gitlab/{targets.go => apiClient.go} | 174 ++++++++++++++-------------- 4 files changed, 115 insertions(+), 87 deletions(-) create mode 100644 common/browser.go create mode 100644 common/interfaces.go rename github/{targets.go => apiClient.go} (86%) rename gitlab/{targets.go => apiClient.go} (100%) diff --git a/common/browser.go b/common/browser.go new file mode 100644 index 00000000..b99a219e --- /dev/null +++ b/common/browser.go @@ -0,0 +1,5 @@ +package common + +import "fmt" + +var UserAgent = fmt.Sprintf("%s v%s", Name, Version) diff --git a/common/interfaces.go b/common/interfaces.go new file mode 100644 index 00000000..3c131e59 --- /dev/null +++ b/common/interfaces.go @@ -0,0 +1,7 @@ +package common + +type IClient interface { + GetUserOrOrganization(login string) (*Owner, error) + GetRepositoriesFromOwner(login *string) ([]*Repository, error) + GetOrganizationMembers(login *string) ([]*Owner, error) +} diff --git a/github/targets.go b/github/apiClient.go similarity index 86% rename from github/targets.go rename to github/apiClient.go index e24b37f9..34215aaa 100644 --- a/github/targets.go +++ b/github/apiClient.go @@ -2,11 +2,27 @@ package github import ( "context" + "net/http" "github.com/codeEmitter/gitrob/common" "github.com/google/go-github/github" + "golang.org/x/oauth2" ) +type client *http.Client + +var clientInstance client + +func init() { + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: nil//s.Github.AccessToken}, + ) + tc := oauth2.NewClient(ctx, ts) + clientInstance = *github.NewClient(tc) + clientInstance.UserAgent = common.UserAgent +} + func GetUserOrOrganization(login string, client *github.Client) (*common.Owner, error) { ctx := context.Background() user, _, err := client.Users.Get(ctx, login) diff --git a/gitlab/targets.go b/gitlab/apiClient.go similarity index 100% rename from gitlab/targets.go rename to gitlab/apiClient.go index 8d9956b0..90494fcb 100644 --- a/gitlab/targets.go +++ b/gitlab/apiClient.go @@ -9,6 +9,93 @@ import ( "github.com/xanzy/go-gitlab" ) +func GetUserOrOrganization(login string, client *gitlab.Client) (*common.Owner, error) { + emptyString := gitlab.String("") + org, orgErr := getOrganization(login, client) + if orgErr != nil { + user, userErr := getUser(login, client) + if userErr != nil { + return nil, userErr + } + id := int64(user.ID) + return &common.Owner{ + Login: gitlab.String(user.Username), + ID: &id, + Type: gitlab.String(common.TargetTypeUser), + Name: gitlab.String(user.Name), + AvatarURL: gitlab.String(user.AvatarURL), + URL: gitlab.String(user.WebsiteURL), + Company: gitlab.String(user.Organization), + Blog: emptyString, + Location: emptyString, + Email: gitlab.String(user.PublicEmail), + Bio: gitlab.String(user.Bio), + }, nil + } else { + id := int64(org.ID) + return &common.Owner{ + Login: gitlab.String(org.Name), + ID: &id, + Type: gitlab.String(common.TargetTypeOrganization), + Name: gitlab.String(org.Name), + AvatarURL: gitlab.String(org.AvatarURL), + URL: gitlab.String(org.WebURL), + Company: gitlab.String(org.FullName), + Blog: emptyString, + Location: emptyString, + Email: emptyString, + Bio: gitlab.String(org.Description), + }, nil + } +} + +func GetOrganizationMembers(id int64, client *gitlab.Client) ([]*common.Owner, error) { + var allMembers []*common.Owner + opt := &gitlab.ListGroupMembersOptions{} + for { + members, resp, err := client.Groups.ListAllGroupMembers(int(id), opt) + if err != nil { + return nil, err + } + for _, member := range members { + id := int64(member.ID) + allMembers = append(allMembers, + &common.Owner{ + Login: gitlab.String(member.Username), + ID: &id, + Type: gitlab.String(common.TargetTypeUser)}) + } + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + return allMembers, nil +} + +func GetRepositoriesFromOwner(target common.Owner, client *gitlab.Client) ([]*common.Repository, error) { + var allProjects []*common.Repository + id := int(*target.ID) + if *target.Type == common.TargetTypeUser { + userProjects, err := getUserProjects(id, client) + if err != nil { + return nil, err + } + for _, project := range userProjects { + allProjects = append(allProjects, project) + } + } else { + groupProjects, err := getGroupProjects(id, client) + if err != nil { + return nil, err + } + for _, project := range groupProjects { + allProjects = append(allProjects, project) + } + } + return allProjects, nil +} + func getUser(login string, client *gitlab.Client) (*gitlab.User, error) { users, _, err := client.Users.ListUsers(&gitlab.ListUsersOptions{Username: gitlab.String(login)}) if err != nil { @@ -106,90 +193,3 @@ func getGroupProjects(id int, client *gitlab.Client) ([]*common.Repository, erro } return allGroupProjects, nil } - -func GetUserOrOrganization(login string, client *gitlab.Client) (*common.Owner, error) { - emptyString := gitlab.String("") - org, orgErr := getOrganization(login, client) - if orgErr != nil { - user, userErr := getUser(login, client) - if userErr != nil { - return nil, userErr - } - id := int64(user.ID) - return &common.Owner{ - Login: gitlab.String(user.Username), - ID: &id, - Type: gitlab.String(common.TargetTypeUser), - Name: gitlab.String(user.Name), - AvatarURL: gitlab.String(user.AvatarURL), - URL: gitlab.String(user.WebsiteURL), - Company: gitlab.String(user.Organization), - Blog: emptyString, - Location: emptyString, - Email: gitlab.String(user.PublicEmail), - Bio: gitlab.String(user.Bio), - }, nil - } else { - id := int64(org.ID) - return &common.Owner{ - Login: gitlab.String(org.Name), - ID: &id, - Type: gitlab.String(common.TargetTypeOrganization), - Name: gitlab.String(org.Name), - AvatarURL: gitlab.String(org.AvatarURL), - URL: gitlab.String(org.WebURL), - Company: gitlab.String(org.FullName), - Blog: emptyString, - Location: emptyString, - Email: emptyString, - Bio: gitlab.String(org.Description), - }, nil - } -} - -func GetOrganizationMembers(id int64, client *gitlab.Client) ([]*common.Owner, error) { - var allMembers []*common.Owner - opt := &gitlab.ListGroupMembersOptions{} - for { - members, resp, err := client.Groups.ListAllGroupMembers(int(id), opt) - if err != nil { - return nil, err - } - for _, member := range members { - id := int64(member.ID) - allMembers = append(allMembers, - &common.Owner{ - Login: gitlab.String(member.Username), - ID: &id, - Type: gitlab.String(common.TargetTypeUser)}) - } - if resp.NextPage == 0 { - break - } - opt.Page = resp.NextPage - } - return allMembers, nil -} - -func GetRepositoriesFromOwner(target common.Owner, client *gitlab.Client) ([]*common.Repository, error) { - var allProjects []*common.Repository - id := int(*target.ID) - if *target.Type == common.TargetTypeUser { - userProjects, err := getUserProjects(id, client) - if err != nil { - return nil, err - } - for _, project := range userProjects { - allProjects = append(allProjects, project) - } - } else { - groupProjects, err := getGroupProjects(id, client) - if err != nil { - return nil, err - } - for _, project := range groupProjects { - allProjects = append(allProjects, project) - } - } - return allProjects, nil -} From 68447d39d8da11834196fa7a07538285a8aea024 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Wed, 11 Mar 2020 17:29:50 -0400 Subject: [PATCH 057/226] refactor banner constants --- {core => common}/banner.go | 2 +- core/session.go | 2 +- main.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename {core => common}/banner.go (95%) diff --git a/core/banner.go b/common/banner.go similarity index 95% rename from core/banner.go rename to common/banner.go index b8b4ec24..fb13ccc9 100644 --- a/core/banner.go +++ b/common/banner.go @@ -1,4 +1,4 @@ -package core +package common const ( Name = "gitrob" diff --git a/core/session.go b/core/session.go index d36f855b..f57c3925 100644 --- a/core/session.go +++ b/core/session.go @@ -266,7 +266,7 @@ func NewSession() (*Session, error) { } } - session.Version = Version + session.Version = common.Version session.Start() return &session, nil diff --git a/main.go b/main.go index 6d02940b..f5f11662 100644 --- a/main.go +++ b/main.go @@ -253,8 +253,8 @@ func main() { os.Exit(1) } - sess.Out.Info("%s\n\n", core.ASCIIBanner) - sess.Out.Important("%s v%s started at %s\n", core.Name, core.Version, sess.Stats.StartedAt.Format(time.RFC3339)) + sess.Out.Info("%s\n\n", common.ASCIIBanner) + sess.Out.Important("%s v%s started at %s\n", common.Name, common.Version, sess.Stats.StartedAt.Format(time.RFC3339)) sess.Out.Important("Loaded %d signatures\n", len(core.Signatures)) sess.Out.Important("Web interface available at http://%s:%d\n", *sess.Options.BindAddress, *sess.Options.Port) From 9b155ecd6f455157200e38f25f4ca58b917be1ed Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Wed, 11 Mar 2020 17:32:30 -0400 Subject: [PATCH 058/226] session maintains a client through interface polymorphism --- common/interfaces.go | 4 ++-- core/session.go | 27 +++++++------------------- github/apiClient.go | 30 ++++++++++++++--------------- gitlab/apiClient.go | 46 +++++++++++++++++++++++++++++--------------- main.go | 25 +++--------------------- 5 files changed, 56 insertions(+), 76 deletions(-) diff --git a/common/interfaces.go b/common/interfaces.go index 3c131e59..e83e02a5 100644 --- a/common/interfaces.go +++ b/common/interfaces.go @@ -2,6 +2,6 @@ package common type IClient interface { GetUserOrOrganization(login string) (*Owner, error) - GetRepositoriesFromOwner(login *string) ([]*Repository, error) - GetOrganizationMembers(login *string) ([]*Owner, error) + GetRepositoriesFromOwner(target Owner) ([]*Repository, error) + GetOrganizationMembers(login string) ([]*Owner, error) } diff --git a/core/session.go b/core/session.go index f57c3925..396b0758 100644 --- a/core/session.go +++ b/core/session.go @@ -1,7 +1,6 @@ package core import ( - "context" "encoding/json" "errors" "fmt" @@ -12,10 +11,9 @@ import ( "time" "github.com/codeEmitter/gitrob/common" + gh "github.com/codeEmitter/gitrob/github" + gl "github.com/codeEmitter/gitrob/gitlab" "github.com/gin-gonic/gin" - "github.com/google/go-github/github" - "github.com/xanzy/go-gitlab" - "golang.org/x/oauth2" ) const ( @@ -42,14 +40,11 @@ type Stats struct { } type Github struct { - AccessToken string `json:"-"` - Client *github.Client `json:"-"` + AccessToken string `json:"-"` } type GitLab struct { AccessToken string `json:"-"` - Client *gitlab.Client - UserID int64 } type Session struct { @@ -61,6 +56,7 @@ type Session struct { Stats *Stats Github Github GitLab GitLab + Client common.IClient Router *gin.Engine `json:"-"` Targets []*common.Owner Repositories []*common.Repository @@ -155,19 +151,10 @@ func (s *Session) ValidateTokenConfig() { } func (s *Session) InitAPIClient() { - userAgent := fmt.Sprintf("%s v%s", Name, Version) if s.Github.AccessToken != "" { - ctx := context.Background() - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: s.Github.AccessToken}, - ) - tc := oauth2.NewClient(ctx, ts) - s.Github.Client = github.NewClient(tc) - s.Github.Client.UserAgent = userAgent - } - if s.GitLab.AccessToken != "" { - s.GitLab.Client = gitlab.NewClient(nil, s.GitLab.AccessToken) - s.GitLab.Client.UserAgent = userAgent + s.Client = gh.Client.NewClient(gh.Client{}, s.Github.AccessToken) + } else { + s.Client = gl.Client.NewClient(gl.Client{}, s.GitLab.AccessToken) } } diff --git a/github/apiClient.go b/github/apiClient.go index 34215aaa..3b7d073c 100644 --- a/github/apiClient.go +++ b/github/apiClient.go @@ -2,30 +2,30 @@ package github import ( "context" - "net/http" "github.com/codeEmitter/gitrob/common" "github.com/google/go-github/github" "golang.org/x/oauth2" ) -type client *http.Client - -var clientInstance client +type Client struct { + apiClient *github.Client +} -func init() { +func (c Client) NewClient(token string) (apiClient Client) { ctx := context.Background() ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: nil//s.Github.AccessToken}, + &oauth2.Token{AccessToken: token}, ) tc := oauth2.NewClient(ctx, ts) - clientInstance = *github.NewClient(tc) - clientInstance.UserAgent = common.UserAgent + c.apiClient = github.NewClient(tc) + c.apiClient.UserAgent = common.UserAgent + return c } -func GetUserOrOrganization(login string, client *github.Client) (*common.Owner, error) { +func (c Client) GetUserOrOrganization(login string) (*common.Owner, error) { ctx := context.Background() - user, _, err := client.Users.Get(ctx, login) + user, _, err := c.apiClient.Users.Get(ctx, login) if err != nil { return nil, err } @@ -44,16 +44,15 @@ func GetUserOrOrganization(login string, client *github.Client) (*common.Owner, }, nil } -func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*common.Repository, error) { +func (c Client) GetRepositoriesFromOwner(target common.Owner) ([]*common.Repository, error) { var allRepos []*common.Repository - loginVal := *login ctx := context.Background() opt := &github.RepositoryListOptions{ Type: "sources", } for { - repos, resp, err := client.Repositories.List(ctx, loginVal, opt) + repos, resp, err := c.apiClient.Repositories.List(ctx, *target.Login, opt) if err != nil { return allRepos, err } @@ -82,13 +81,12 @@ func GetRepositoriesFromOwner(login *string, client *github.Client) ([]*common.R return allRepos, nil } -func GetOrganizationMembers(login *string, client *github.Client) ([]*common.Owner, error) { +func (c Client) GetOrganizationMembers(login string) ([]*common.Owner, error) { var allMembers []*common.Owner - loginVal := *login ctx := context.Background() opt := &github.ListMembersOptions{} for { - members, resp, err := client.Organizations.ListMembers(ctx, loginVal, opt) + members, resp, err := c.apiClient.Organizations.ListMembers(ctx, login, opt) if err != nil { return allMembers, err } diff --git a/gitlab/apiClient.go b/gitlab/apiClient.go index 90494fcb..aa20d7db 100644 --- a/gitlab/apiClient.go +++ b/gitlab/apiClient.go @@ -9,11 +9,21 @@ import ( "github.com/xanzy/go-gitlab" ) -func GetUserOrOrganization(login string, client *gitlab.Client) (*common.Owner, error) { +type Client struct { + apiClient *gitlab.Client +} + +func (c Client) NewClient(token string) (apiClient Client) { + c.apiClient = gitlab.NewClient(nil, token) + c.apiClient.UserAgent = common.UserAgent + return c +} + +func (c Client) GetUserOrOrganization(login string) (*common.Owner, error) { emptyString := gitlab.String("") - org, orgErr := getOrganization(login, client) + org, orgErr := c.getOrganization(login) if orgErr != nil { - user, userErr := getUser(login, client) + user, userErr := c.getUser(login) if userErr != nil { return nil, userErr } @@ -49,11 +59,15 @@ func GetUserOrOrganization(login string, client *gitlab.Client) (*common.Owner, } } -func GetOrganizationMembers(id int64, client *gitlab.Client) ([]*common.Owner, error) { +func (c Client) GetOrganizationMembers(login string) ([]*common.Owner, error) { var allMembers []*common.Owner opt := &gitlab.ListGroupMembersOptions{} + id, err := strconv.Atoi(login) + if err != nil { + return nil, err + } for { - members, resp, err := client.Groups.ListAllGroupMembers(int(id), opt) + members, resp, err := c.apiClient.Groups.ListAllGroupMembers(id, opt) if err != nil { return nil, err } @@ -73,11 +87,11 @@ func GetOrganizationMembers(id int64, client *gitlab.Client) ([]*common.Owner, e return allMembers, nil } -func GetRepositoriesFromOwner(target common.Owner, client *gitlab.Client) ([]*common.Repository, error) { +func (c Client) GetRepositoriesFromOwner(target common.Owner) ([]*common.Repository, error) { var allProjects []*common.Repository id := int(*target.ID) if *target.Type == common.TargetTypeUser { - userProjects, err := getUserProjects(id, client) + userProjects, err := c.getUserProjects(id) if err != nil { return nil, err } @@ -85,7 +99,7 @@ func GetRepositoriesFromOwner(target common.Owner, client *gitlab.Client) ([]*co allProjects = append(allProjects, project) } } else { - groupProjects, err := getGroupProjects(id, client) + groupProjects, err := c.getGroupProjects(id) if err != nil { return nil, err } @@ -96,8 +110,8 @@ func GetRepositoriesFromOwner(target common.Owner, client *gitlab.Client) ([]*co return allProjects, nil } -func getUser(login string, client *gitlab.Client) (*gitlab.User, error) { - users, _, err := client.Users.ListUsers(&gitlab.ListUsersOptions{Username: gitlab.String(login)}) +func (c Client) getUser(login string) (*gitlab.User, error) { + users, _, err := c.apiClient.Users.ListUsers(&gitlab.ListUsersOptions{Username: gitlab.String(login)}) if err != nil { return nil, err } @@ -110,23 +124,23 @@ func getUser(login string, client *gitlab.Client) (*gitlab.User, error) { return users[0], err } -func getOrganization(login string, client *gitlab.Client) (*gitlab.Group, error) { +func (c Client) getOrganization(login string) (*gitlab.Group, error) { id, err := strconv.Atoi(login) if err != nil { return nil, err } - org, _, err := client.Groups.GetGroup(id) + org, _, err := c.apiClient.Groups.GetGroup(id) if err != nil { return nil, err } return org, err } -func getUserProjects(id int, client *gitlab.Client) ([]*common.Repository, error) { +func (c Client) getUserProjects(id int) ([]*common.Repository, error) { var allUserProjects []*common.Repository listUserProjectsOps := &gitlab.ListProjectsOptions{} for { - projects, resp, err := client.Projects.ListUserProjects(id, listUserProjectsOps) + projects, resp, err := c.apiClient.Projects.ListUserProjects(id, listUserProjectsOps) if err != nil { return nil, err } @@ -156,11 +170,11 @@ func getUserProjects(id int, client *gitlab.Client) ([]*common.Repository, error return allUserProjects, nil } -func getGroupProjects(id int, client *gitlab.Client) ([]*common.Repository, error) { +func (c Client) getGroupProjects(id int) ([]*common.Repository, error) { var allGroupProjects []*common.Repository listGroupProjectsOps := &gitlab.ListGroupProjectsOptions{} for { - projects, resp, err := client.Groups.ListGroupProjects(id, listGroupProjectsOps) + projects, resp, err := c.apiClient.Groups.ListGroupProjects(id, listGroupProjectsOps) if err != nil { return nil, err } diff --git a/main.go b/main.go index f5f11662..9cb657e8 100644 --- a/main.go +++ b/main.go @@ -24,14 +24,7 @@ func GatherTargets(sess *core.Session) { sess.Out.Important("Gathering targets...\n") for _, login := range sess.Options.Logins { - target, err := func() (*common.Owner, error) { - if sess.Github.AccessToken != "" { - return github.GetUserOrOrganization(login, sess.Github.Client) - } else { - return gitlab.GetUserOrOrganization(login, sess.GitLab.Client) - } - }() - + target, err := sess.Client.GetUserOrOrganization(login) if err != nil || target == nil { sess.Out.Error(" Error retrieving information on %s: %s\n", login, err) continue @@ -40,13 +33,7 @@ func GatherTargets(sess *core.Session) { sess.AddTarget(target) if *sess.Options.NoExpandOrgs == false && *target.Type == common.TargetTypeOrganization { sess.Out.Debug("Gathering members of %s (ID: %d)...\n", *target.Login, *target.ID) - members, err := func() ([]*common.Owner, error) { - if sess.Github.AccessToken != "" { - return github.GetOrganizationMembers(target.Login, sess.Github.Client) - } else { - return gitlab.GetOrganizationMembers(*target.ID, sess.GitLab.Client) - } - }() + members, err := sess.Client.GetOrganizationMembers(*target.Login) if err != nil { sess.Out.Error(" Error retrieving members of %s: %s\n", *target.Login, err) continue @@ -80,13 +67,7 @@ func GatherRepositories(sess *core.Session) { wg.Done() return } - repos, err := func() ([]*common.Repository, error) { - if sess.Github.AccessToken != "" { - return github.GetRepositoriesFromOwner(target.Login, sess.Github.Client) - } else { - return gitlab.GetRepositoriesFromOwner(*target, sess.GitLab.Client) - } - }() + repos, err := sess.Client.GetRepositoriesFromOwner(*target) if err != nil { sess.Out.Error(" Failed to retrieve repositories from %s: %s\n", *target.Login, err) } From 444e14282a739da5667d9c731c965aeeef714644 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 12 Mar 2020 13:23:51 -0400 Subject: [PATCH 059/226] dont require an api token to load results --- core/session.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/core/session.go b/core/session.go index 396b0758..6c6d7e8e 100644 --- a/core/session.go +++ b/core/session.go @@ -142,11 +142,13 @@ func (s *Session) InitAccessToken() { } func (s *Session) ValidateTokenConfig() { - if s.GitLab.AccessToken != "" && s.Github.AccessToken != "" { - s.Out.Fatal("Both a GitLab and Github token are present. Only one may be set.") - } - if s.GitLab.AccessToken == "" && s.Github.AccessToken == "" { - s.Out.Fatal("No valid API token was found.\n") + if *s.Options.Load == "" { + if s.GitLab.AccessToken != "" && s.Github.AccessToken != "" { + s.Out.Fatal("Both a GitLab and Github token are present. Only one may be set.") + } + if s.GitLab.AccessToken == "" && s.Github.AccessToken == "" { + s.Out.Fatal("No valid API token was found.\n") + } } } From 586a9e88bdaddd6f800b0ffe1d2b068a5484d95b Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 12 Mar 2020 13:24:29 -0400 Subject: [PATCH 060/226] block unmarshaling of unnecessary session props --- core/session.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/session.go b/core/session.go index 6c6d7e8e..eea42ab7 100644 --- a/core/session.go +++ b/core/session.go @@ -51,13 +51,13 @@ type Session struct { sync.Mutex Version string - Options Options `json:"-"` - Out *Logger `json:"-"` + Options Options `json:"-"` //do not unmarshal to json on save + Out *Logger `json:"-"` //do not unmarshal to json on save Stats *Stats - Github Github - GitLab GitLab - Client common.IClient - Router *gin.Engine `json:"-"` + Github Github `json:"-"` //do not unmarshal to json on save + GitLab GitLab `json:"-"` //do not unmarshal to json on save + Client common.IClient `json:"-"` //do not unmarshal to json on save + Router *gin.Engine `json:"-"` //do not unmarshal to json on save Targets []*common.Owner Repositories []*common.Repository Findings []*Finding From fdb588065880d77726497470eb53c101b89e9523 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 12 Mar 2020 16:43:04 -0400 Subject: [PATCH 061/226] rename method for better clarity --- core/session.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/session.go b/core/session.go index eea42ab7..b69466d9 100644 --- a/core/session.go +++ b/core/session.go @@ -63,7 +63,7 @@ type Session struct { Findings []*Finding } -func (s *Session) Start() { +func (s *Session) Initialize() { s.InitStats() s.InitLogger() s.InitThreads() @@ -256,7 +256,7 @@ func NewSession() (*Session, error) { } session.Version = common.Version - session.Start() + session.Initialize() return &session, nil } From 92daa0284cb568bfe3d9f2518425c2f1c1cd7111 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 12 Mar 2020 16:45:09 -0400 Subject: [PATCH 062/226] replace spaces with dashes for gitlab url conformity --- core/signatures.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/signatures.go b/core/signatures.go index b453b3b1..b8c2cad9 100644 --- a/core/signatures.go +++ b/core/signatures.go @@ -65,7 +65,9 @@ func (f *Finding) setupUrls(isGithubSession bool) { f.FileUrl = fmt.Sprintf("%s/blob/%s/%s", f.RepositoryUrl, f.CommitHash, f.FilePath) f.CommitUrl = fmt.Sprintf("%s/commit/%s", f.RepositoryUrl, f.CommitHash) } else { - f.RepositoryUrl = fmt.Sprintf("https://gitlab.com/%s/%s", f.RepositoryOwner, f.RepositoryName) + repoOwner := strings.ReplaceAll(f.RepositoryOwner, " ", "-") + repoName := strings.ReplaceAll(f.RepositoryName, " ", "-") + f.RepositoryUrl = fmt.Sprintf("https://gitlab.com/%s/%s", repoOwner, repoName) f.FileUrl = fmt.Sprintf("%s/blob/%s/%s", f.RepositoryUrl, f.CommitHash, f.FilePath) f.CommitUrl = fmt.Sprintf("%s/commit/%s", f.RepositoryUrl, f.CommitHash) } From 5fb4ca38e0a92dd43773ebf5548657b907f16b7d Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 12 Mar 2020 16:51:16 -0400 Subject: [PATCH 063/226] rename var for better clarity --- main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 9cb657e8..67d59242 100644 --- a/main.go +++ b/main.go @@ -23,10 +23,10 @@ func GatherTargets(sess *core.Session) { sess.Stats.Status = core.StatusGathering sess.Out.Important("Gathering targets...\n") - for _, login := range sess.Options.Logins { - target, err := sess.Client.GetUserOrOrganization(login) + for _, loginOption := range sess.Options.Logins { + target, err := sess.Client.GetUserOrOrganization(loginOption) if err != nil || target == nil { - sess.Out.Error(" Error retrieving information on %s: %s\n", login, err) + sess.Out.Error(" Error retrieving information on %s: %s\n", loginOption, err) continue } sess.Out.Debug("%s (ID: %d) type: %s\n", *target.Login, *target.ID, *target.Type) From cdb031fdc61d81d2894618daf25999ea3e64d602 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 12 Mar 2020 17:47:48 -0400 Subject: [PATCH 064/226] GetOrgMembers takes a Owner type as a common denominator --- common/interfaces.go | 2 +- github/apiClient.go | 4 ++-- gitlab/apiClient.go | 9 +++------ main.go | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/common/interfaces.go b/common/interfaces.go index e83e02a5..3f58e68c 100644 --- a/common/interfaces.go +++ b/common/interfaces.go @@ -3,5 +3,5 @@ package common type IClient interface { GetUserOrOrganization(login string) (*Owner, error) GetRepositoriesFromOwner(target Owner) ([]*Repository, error) - GetOrganizationMembers(login string) ([]*Owner, error) + GetOrganizationMembers(target Owner) ([]*Owner, error) } diff --git a/github/apiClient.go b/github/apiClient.go index 3b7d073c..4d4e41e6 100644 --- a/github/apiClient.go +++ b/github/apiClient.go @@ -81,12 +81,12 @@ func (c Client) GetRepositoriesFromOwner(target common.Owner) ([]*common.Reposit return allRepos, nil } -func (c Client) GetOrganizationMembers(login string) ([]*common.Owner, error) { +func (c Client) GetOrganizationMembers(target common.Owner) ([]*common.Owner, error) { var allMembers []*common.Owner ctx := context.Background() opt := &github.ListMembersOptions{} for { - members, resp, err := c.apiClient.Organizations.ListMembers(ctx, login, opt) + members, resp, err := c.apiClient.Organizations.ListMembers(ctx, *target.Login, opt) if err != nil { return allMembers, err } diff --git a/gitlab/apiClient.go b/gitlab/apiClient.go index aa20d7db..361eb01e 100644 --- a/gitlab/apiClient.go +++ b/gitlab/apiClient.go @@ -59,15 +59,12 @@ func (c Client) GetUserOrOrganization(login string) (*common.Owner, error) { } } -func (c Client) GetOrganizationMembers(login string) ([]*common.Owner, error) { +func (c Client) GetOrganizationMembers(target common.Owner) ([]*common.Owner, error) { var allMembers []*common.Owner opt := &gitlab.ListGroupMembersOptions{} - id, err := strconv.Atoi(login) - if err != nil { - return nil, err - } + sID := strconv.FormatInt(*target.ID, 10) //safely downcast an int64 to an int for { - members, resp, err := c.apiClient.Groups.ListAllGroupMembers(id, opt) + members, resp, err := c.apiClient.Groups.ListAllGroupMembers(sID, opt) if err != nil { return nil, err } diff --git a/main.go b/main.go index 67d59242..3fd597af 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ func GatherTargets(sess *core.Session) { sess.AddTarget(target) if *sess.Options.NoExpandOrgs == false && *target.Type == common.TargetTypeOrganization { sess.Out.Debug("Gathering members of %s (ID: %d)...\n", *target.Login, *target.ID) - members, err := sess.Client.GetOrganizationMembers(*target.Login) + members, err := sess.Client.GetOrganizationMembers(*target) if err != nil { sess.Out.Error(" Error retrieving members of %s: %s\n", *target.Login, err) continue From 7c09a8f46fcb06fa33dc659456535b519ab4bcbe Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 12 Mar 2020 18:33:15 -0400 Subject: [PATCH 065/226] user namespace in place of owner due to a bug in go-gitlab --- gitlab/apiClient.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/gitlab/apiClient.go b/gitlab/apiClient.go index 361eb01e..3640d33e 100644 --- a/gitlab/apiClient.go +++ b/gitlab/apiClient.go @@ -96,7 +96,7 @@ func (c Client) GetRepositoriesFromOwner(target common.Owner) ([]*common.Reposit allProjects = append(allProjects, project) } } else { - groupProjects, err := c.getGroupProjects(id) + groupProjects, err := c.getGroupProjects(target) if err != nil { return nil, err } @@ -167,9 +167,10 @@ func (c Client) getUserProjects(id int) ([]*common.Repository, error) { return allUserProjects, nil } -func (c Client) getGroupProjects(id int) ([]*common.Repository, error) { +func (c Client) getGroupProjects(target common.Owner) ([]*common.Repository, error) { var allGroupProjects []*common.Repository listGroupProjectsOps := &gitlab.ListGroupProjectsOptions{} + id := strconv.FormatInt(*target.ID, 10) for { projects, resp, err := c.apiClient.Groups.ListGroupProjects(id, listGroupProjectsOps) if err != nil { @@ -179,12 +180,8 @@ func (c Client) getGroupProjects(id int) ([]*common.Repository, error) { //don't capture forks if project.ForkedFromProject == nil { id := int64(project.ID) - owner := "" - if project.Owner != nil { - owner = project.Owner.Name - } p := common.Repository{ - Owner: gitlab.String(owner), + Owner: gitlab.String(project.Namespace.FullPath), ID: &id, Name: gitlab.String(project.Name), FullName: gitlab.String(project.NameWithNamespace), From cde4106d11b8e77a3a925677a3b854d3fed8867e Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 14:43:04 -0400 Subject: [PATCH 066/226] replace spaces in url with dashes for gitlab compatibility when retrieving raw results --- common/browser.go | 13 ++++++++++++- core/router.go | 4 +++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/common/browser.go b/common/browser.go index b99a219e..3ebe7e0c 100644 --- a/common/browser.go +++ b/common/browser.go @@ -1,5 +1,16 @@ package common -import "fmt" +import ( + "fmt" + "strings" +) var UserAgent = fmt.Sprintf("%s v%s", Name, Version) + +func CleanUrlSpaces(dirtyStrings ...string) []string { + result := []string{} + for _, s := range dirtyStrings { + result = append(result, strings.ReplaceAll(s, " ", "-")) + } + return result +} diff --git a/core/router.go b/core/router.go index a6dab2c6..3429616e 100644 --- a/core/router.go +++ b/core/router.go @@ -6,6 +6,7 @@ import ( "net/http" "strings" + "github.com/codeEmitter/gitrob/common" assetfs "github.com/elazarl/go-bindata-assetfs" "github.com/gin-contrib/secure" "github.com/gin-contrib/static" @@ -92,7 +93,8 @@ func fetchFile(c *gin.Context) { if IsGithub { return fmt.Sprintf("%s/%s/%s/%s%s", GithubBaseUri, c.Param("owner"), c.Param("repo"), c.Param("commit"), c.Param("path")) } else { - return fmt.Sprintf("%s/%s/%s/%s/%s%s", GitLabBaseUri, c.Param("owner"), c.Param("repo"), "/-/raw/", c.Param("commit"), c.Param("path")) + results := common.CleanUrlSpaces(c.Param("owner"), c.Param("repo"), c.Param("commit"), c.Param("path")) + return fmt.Sprintf("%s/%s/%s/%s/%s%s", GitLabBaseUri, results[0], results[1], "/-/raw/", results[2], results[3]) } }() resp, err := http.Head(fileUrl) From 934495ad598acc50019a199cebd8ed73569ca10c Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 15:18:17 -0400 Subject: [PATCH 067/226] refactor to gathertargets to analysis --- core/analysis.go | 30 ++++++++++++++++++++++++++++++ main.go | 29 +---------------------------- 2 files changed, 31 insertions(+), 28 deletions(-) create mode 100644 core/analysis.go diff --git a/core/analysis.go b/core/analysis.go new file mode 100644 index 00000000..6155dc13 --- /dev/null +++ b/core/analysis.go @@ -0,0 +1,30 @@ +package core + +import "github.com/codeEmitter/gitrob/common" + +func GatherTargets(sess *Session) { + sess.Stats.Status = StatusGathering + sess.Out.Important("Gathering targets...\n") + + for _, loginOption := range sess.Options.Logins { + target, err := sess.Client.GetUserOrOrganization(loginOption) + if err != nil || target == nil { + sess.Out.Error(" Error retrieving information on %s: %s\n", loginOption, err) + continue + } + sess.Out.Debug("%s (ID: %d) type: %s\n", *target.Login, *target.ID, *target.Type) + sess.AddTarget(target) + if *sess.Options.NoExpandOrgs == false && *target.Type == common.TargetTypeOrganization { + sess.Out.Debug("Gathering members of %s (ID: %d)...\n", *target.Login, *target.ID) + members, err := sess.Client.GetOrganizationMembers(*target) + if err != nil { + sess.Out.Error(" Error retrieving members of %s: %s\n", *target.Login, err) + continue + } + for _, member := range members { + sess.Out.Debug("Adding organization member %s (ID: %d) to targets\n", *member.Login, *member.ID) + sess.AddTarget(member) + } + } + } +} diff --git a/main.go b/main.go index 3fd597af..2c523d7e 100644 --- a/main.go +++ b/main.go @@ -19,33 +19,6 @@ var ( err error ) -func GatherTargets(sess *core.Session) { - sess.Stats.Status = core.StatusGathering - sess.Out.Important("Gathering targets...\n") - - for _, loginOption := range sess.Options.Logins { - target, err := sess.Client.GetUserOrOrganization(loginOption) - if err != nil || target == nil { - sess.Out.Error(" Error retrieving information on %s: %s\n", loginOption, err) - continue - } - sess.Out.Debug("%s (ID: %d) type: %s\n", *target.Login, *target.ID, *target.Type) - sess.AddTarget(target) - if *sess.Options.NoExpandOrgs == false && *target.Type == common.TargetTypeOrganization { - sess.Out.Debug("Gathering members of %s (ID: %d)...\n", *target.Login, *target.ID) - members, err := sess.Client.GetOrganizationMembers(*target) - if err != nil { - sess.Out.Error(" Error retrieving members of %s: %s\n", *target.Login, err) - continue - } - for _, member := range members { - sess.Out.Debug("Adding organization member %s (ID: %d) to targets\n", *member.Login, *member.ID) - sess.AddTarget(member) - } - } - } -} - func GatherRepositories(sess *core.Session) { var ch = make(chan *common.Owner, len(sess.Targets)) var wg sync.WaitGroup @@ -253,7 +226,7 @@ func main() { sess.Out.Fatal(fmt.Sprintf("Please provide at least one %s or user\n", host)) } - GatherTargets(sess) + core.GatherTargets(sess) GatherRepositories(sess) AnalyzeRepositories(sess) sess.Finish() From c65c7f2e11d72440133b15b071391e05e24392a2 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 15:26:09 -0400 Subject: [PATCH 068/226] refactor remaining supporting functions --- core/analysis.go | 193 ++++++++++++++++++++++++++++++++++++++++++++++- main.go | 193 +---------------------------------------------- 2 files changed, 195 insertions(+), 191 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index 6155dc13..75a955e1 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -1,6 +1,23 @@ package core -import "github.com/codeEmitter/gitrob/common" +import ( + "github.com/codeEmitter/gitrob/common" + "github.com/codeEmitter/gitrob/github" + "github.com/codeEmitter/gitrob/gitlab" + "gopkg.in/src-d/go-git.v4" + "os" + "strings" + "sync" +) + + +func PrintSessionStats(sess *Session) { + sess.Out.Info("\nFindings....: %d\n", sess.Stats.Findings) + sess.Out.Info("Files.......: %d\n", sess.Stats.Files) + sess.Out.Info("Commits.....: %d\n", sess.Stats.Commits) + sess.Out.Info("Repositories: %d\n", sess.Stats.Repositories) + sess.Out.Info("Targets.....: %d\n\n", sess.Stats.Targets) +} func GatherTargets(sess *Session) { sess.Stats.Status = StatusGathering @@ -28,3 +45,177 @@ func GatherTargets(sess *Session) { } } } + +func GatherRepositories(sess *Session) { + var ch = make(chan *common.Owner, len(sess.Targets)) + var wg sync.WaitGroup + var threadNum int + if len(sess.Targets) == 1 { + threadNum = 1 + } else if len(sess.Targets) <= *sess.Options.Threads { + threadNum = len(sess.Targets) - 1 + } else { + threadNum = *sess.Options.Threads + } + wg.Add(threadNum) + sess.Out.Debug("Threads for repository gathering: %d\n", threadNum) + for i := 0; i < threadNum; i++ { + go func() { + for { + target, ok := <-ch + if !ok { + wg.Done() + return + } + repos, err := sess.Client.GetRepositoriesFromOwner(*target) + if err != nil { + sess.Out.Error(" Failed to retrieve repositories from %s: %s\n", *target.Login, err) + } + if len(repos) == 0 { + continue + } + for _, repo := range repos { + sess.Out.Debug(" Retrieved repository: %s\n", *repo.CloneURL) + sess.AddRepository(repo) + } + sess.Stats.IncrementTargets() + sess.Out.Info(" Retrieved %d %s from %s\n", len(repos), Pluralize(len(repos), "repository", "repositories"), *target.Login) + } + }() + } + + for _, target := range sess.Targets { + ch <- target + } + close(ch) + wg.Wait() +} + +func AnalyzeRepositories(sess *Session) { + sess.Stats.Status = StatusAnalyzing + var ch = make(chan *common.Repository, len(sess.Repositories)) + var wg sync.WaitGroup + var threadNum int + if len(sess.Repositories) <= 1 { + threadNum = 1 + } else if len(sess.Repositories) <= *sess.Options.Threads { + threadNum = len(sess.Repositories) - 1 + } else { + threadNum = *sess.Options.Threads + } + wg.Add(threadNum) + sess.Out.Debug("Threads for repository analysis: %d\n", threadNum) + + sess.Out.Important("Analyzing %d %s...\n", len(sess.Repositories), Pluralize(len(sess.Repositories), "repository", "repositories")) + + for i := 0; i < threadNum; i++ { + go func(tid int) { + for { + sess.Out.Debug("[THREAD #%d] Requesting new repository to analyze...\n", tid) + repo, ok := <-ch + if !ok { + sess.Out.Debug("[THREAD #%d] No more tasks, marking WaitGroup as done\n", tid) + wg.Done() + return + } + + sess.Out.Debug("[THREAD #%d][%s] Cloning repository...\n", tid, *repo.CloneURL) + clone, path, err := func() (*git.Repository, string, error) { + cloneConfig := common.CloneConfiguration{ + Url: repo.CloneURL, + Branch: repo.DefaultBranch, + Depth: sess.Options.CommitDepth, + Token: &sess.GitLab.AccessToken, + } + if sess.Github.AccessToken != "" { + return github.CloneRepository(&cloneConfig) + } else { + userName := "oauth2" + cloneConfig.Username = &userName + return gitlab.CloneRepository(&cloneConfig) + } + }() + if err != nil { + if err.Error() != "remote repository is empty" { + sess.Out.Error("Error cloning repository %s: %s\n", *repo.CloneURL, err) + } + sess.Stats.IncrementRepositories() + sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) + continue + } + sess.Out.Debug("[THREAD #%d][%s] Cloned repository to: %s\n", tid, *repo.CloneURL, path) + + history, err := common.GetRepositoryHistory(clone) + if err != nil { + sess.Out.Error("[THREAD #%d][%s] Error getting commit history: %s\n", tid, *repo.CloneURL, err) + os.RemoveAll(path) + sess.Stats.IncrementRepositories() + sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) + continue + } + sess.Out.Debug("[THREAD #%d][%s] Number of commits: %d\n", tid, *repo.CloneURL, len(history)) + + for _, commit := range history { + sess.Out.Debug("[THREAD #%d][%s] Analyzing commit: %s\n", tid, *repo.CloneURL, commit.Hash) + changes, _ := common.GetChanges(commit, clone) + sess.Out.Debug("[THREAD #%d][%s] Changes in %s: %d\n", tid, *repo.CloneURL, commit.Hash, len(changes)) + for _, change := range changes { + changeAction := common.GetChangeAction(change) + path := common.GetChangePath(change) + matchFile := NewMatchFile(path) + if matchFile.IsSkippable() { + sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.CloneURL, matchFile.Path) + continue + } + sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.CloneURL, matchFile.Path) + for _, signature := range Signatures { + if signature.Match(matchFile) { + + finding := &Finding{ + FilePath: path, + Action: changeAction, + Description: signature.Description(), + Comment: signature.Comment(), + RepositoryOwner: *repo.Owner, + RepositoryName: *repo.Name, + CommitHash: commit.Hash.String(), + CommitMessage: strings.TrimSpace(commit.Message), + CommitAuthor: commit.Author.String(), + } + finding.Initialize(sess.Github.AccessToken != "") + sess.AddFinding(finding) + + sess.Out.Warn(" %s: %s\n", strings.ToUpper(changeAction), finding.Description) + sess.Out.Info(" Path.......: %s\n", finding.FilePath) + sess.Out.Info(" Repo.......: %s\n", *repo.CloneURL) + sess.Out.Info(" Message....: %s\n", TruncateString(finding.CommitMessage, 100)) + sess.Out.Info(" Author.....: %s\n", finding.CommitAuthor) + if finding.Comment != "" { + sess.Out.Info(" Comment....: %s\n", finding.Comment) + } + sess.Out.Info(" File URL...: %s\n", finding.FileUrl) + sess.Out.Info(" Commit URL.: %s\n", finding.CommitUrl) + sess.Out.Info(" ------------------------------------------------\n\n") + sess.Stats.IncrementFindings() + break + } + } + sess.Stats.IncrementFiles() + } + sess.Stats.IncrementCommits() + sess.Out.Debug("[THREAD #%d][%s] Done analyzing changes in %s\n", tid, *repo.CloneURL, commit.Hash) + } + sess.Out.Debug("[THREAD #%d][%s] Done analyzing commits\n", tid, *repo.CloneURL) + os.RemoveAll(path) + sess.Out.Debug("[THREAD #%d][%s] Deleted %s\n", tid, *repo.CloneURL, path) + sess.Stats.IncrementRepositories() + sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) + } + }(i) + } + for _, repo := range sess.Repositories { + ch <- repo + } + close(ch) + wg.Wait() +} \ No newline at end of file diff --git a/main.go b/main.go index 2c523d7e..9dda025a 100644 --- a/main.go +++ b/main.go @@ -3,15 +3,10 @@ package main import ( "fmt" "os" - "strings" - "sync" "time" "github.com/codeEmitter/gitrob/common" "github.com/codeEmitter/gitrob/core" - "github.com/codeEmitter/gitrob/github" - "github.com/codeEmitter/gitrob/gitlab" - "gopkg.in/src-d/go-git.v4" ) var ( @@ -19,188 +14,6 @@ var ( err error ) -func GatherRepositories(sess *core.Session) { - var ch = make(chan *common.Owner, len(sess.Targets)) - var wg sync.WaitGroup - var threadNum int - if len(sess.Targets) == 1 { - threadNum = 1 - } else if len(sess.Targets) <= *sess.Options.Threads { - threadNum = len(sess.Targets) - 1 - } else { - threadNum = *sess.Options.Threads - } - wg.Add(threadNum) - sess.Out.Debug("Threads for repository gathering: %d\n", threadNum) - for i := 0; i < threadNum; i++ { - go func() { - for { - target, ok := <-ch - if !ok { - wg.Done() - return - } - repos, err := sess.Client.GetRepositoriesFromOwner(*target) - if err != nil { - sess.Out.Error(" Failed to retrieve repositories from %s: %s\n", *target.Login, err) - } - if len(repos) == 0 { - continue - } - for _, repo := range repos { - sess.Out.Debug(" Retrieved repository: %s\n", *repo.CloneURL) - sess.AddRepository(repo) - } - sess.Stats.IncrementTargets() - sess.Out.Info(" Retrieved %d %s from %s\n", len(repos), core.Pluralize(len(repos), "repository", "repositories"), *target.Login) - } - }() - } - - for _, target := range sess.Targets { - ch <- target - } - close(ch) - wg.Wait() -} - -func AnalyzeRepositories(sess *core.Session) { - sess.Stats.Status = core.StatusAnalyzing - var ch = make(chan *common.Repository, len(sess.Repositories)) - var wg sync.WaitGroup - var threadNum int - if len(sess.Repositories) <= 1 { - threadNum = 1 - } else if len(sess.Repositories) <= *sess.Options.Threads { - threadNum = len(sess.Repositories) - 1 - } else { - threadNum = *sess.Options.Threads - } - wg.Add(threadNum) - sess.Out.Debug("Threads for repository analysis: %d\n", threadNum) - - sess.Out.Important("Analyzing %d %s...\n", len(sess.Repositories), core.Pluralize(len(sess.Repositories), "repository", "repositories")) - - for i := 0; i < threadNum; i++ { - go func(tid int) { - for { - sess.Out.Debug("[THREAD #%d] Requesting new repository to analyze...\n", tid) - repo, ok := <-ch - if !ok { - sess.Out.Debug("[THREAD #%d] No more tasks, marking WaitGroup as done\n", tid) - wg.Done() - return - } - - sess.Out.Debug("[THREAD #%d][%s] Cloning repository...\n", tid, *repo.CloneURL) - clone, path, err := func() (*git.Repository, string, error) { - cloneConfig := common.CloneConfiguration{ - Url: repo.CloneURL, - Branch: repo.DefaultBranch, - Depth: sess.Options.CommitDepth, - Token: &sess.GitLab.AccessToken, - } - if sess.Github.AccessToken != "" { - return github.CloneRepository(&cloneConfig) - } else { - userName := "oauth2" - cloneConfig.Username = &userName - return gitlab.CloneRepository(&cloneConfig) - } - }() - if err != nil { - if err.Error() != "remote repository is empty" { - sess.Out.Error("Error cloning repository %s: %s\n", *repo.CloneURL, err) - } - sess.Stats.IncrementRepositories() - sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) - continue - } - sess.Out.Debug("[THREAD #%d][%s] Cloned repository to: %s\n", tid, *repo.CloneURL, path) - - history, err := common.GetRepositoryHistory(clone) - if err != nil { - sess.Out.Error("[THREAD #%d][%s] Error getting commit history: %s\n", tid, *repo.CloneURL, err) - os.RemoveAll(path) - sess.Stats.IncrementRepositories() - sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) - continue - } - sess.Out.Debug("[THREAD #%d][%s] Number of commits: %d\n", tid, *repo.CloneURL, len(history)) - - for _, commit := range history { - sess.Out.Debug("[THREAD #%d][%s] Analyzing commit: %s\n", tid, *repo.CloneURL, commit.Hash) - changes, _ := common.GetChanges(commit, clone) - sess.Out.Debug("[THREAD #%d][%s] Changes in %s: %d\n", tid, *repo.CloneURL, commit.Hash, len(changes)) - for _, change := range changes { - changeAction := common.GetChangeAction(change) - path := common.GetChangePath(change) - matchFile := core.NewMatchFile(path) - if matchFile.IsSkippable() { - sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.CloneURL, matchFile.Path) - continue - } - sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.CloneURL, matchFile.Path) - for _, signature := range core.Signatures { - if signature.Match(matchFile) { - - finding := &core.Finding{ - FilePath: path, - Action: changeAction, - Description: signature.Description(), - Comment: signature.Comment(), - RepositoryOwner: *repo.Owner, - RepositoryName: *repo.Name, - CommitHash: commit.Hash.String(), - CommitMessage: strings.TrimSpace(commit.Message), - CommitAuthor: commit.Author.String(), - } - finding.Initialize(sess.Github.AccessToken != "") - sess.AddFinding(finding) - - sess.Out.Warn(" %s: %s\n", strings.ToUpper(changeAction), finding.Description) - sess.Out.Info(" Path.......: %s\n", finding.FilePath) - sess.Out.Info(" Repo.......: %s\n", *repo.CloneURL) - sess.Out.Info(" Message....: %s\n", core.TruncateString(finding.CommitMessage, 100)) - sess.Out.Info(" Author.....: %s\n", finding.CommitAuthor) - if finding.Comment != "" { - sess.Out.Info(" Comment....: %s\n", finding.Comment) - } - sess.Out.Info(" File URL...: %s\n", finding.FileUrl) - sess.Out.Info(" Commit URL.: %s\n", finding.CommitUrl) - sess.Out.Info(" ------------------------------------------------\n\n") - sess.Stats.IncrementFindings() - break - } - } - sess.Stats.IncrementFiles() - } - sess.Stats.IncrementCommits() - sess.Out.Debug("[THREAD #%d][%s] Done analyzing changes in %s\n", tid, *repo.CloneURL, commit.Hash) - } - sess.Out.Debug("[THREAD #%d][%s] Done analyzing commits\n", tid, *repo.CloneURL) - os.RemoveAll(path) - sess.Out.Debug("[THREAD #%d][%s] Deleted %s\n", tid, *repo.CloneURL, path) - sess.Stats.IncrementRepositories() - sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) - } - }(i) - } - for _, repo := range sess.Repositories { - ch <- repo - } - close(ch) - wg.Wait() -} - -func PrintSessionStats(sess *core.Session) { - sess.Out.Info("\nFindings....: %d\n", sess.Stats.Findings) - sess.Out.Info("Files.......: %d\n", sess.Stats.Files) - sess.Out.Info("Commits.....: %d\n", sess.Stats.Commits) - sess.Out.Info("Repositories: %d\n", sess.Stats.Repositories) - sess.Out.Info("Targets.....: %d\n\n", sess.Stats.Targets) -} - func main() { if sess, err = core.NewSession(); err != nil { fmt.Println(err) @@ -227,8 +40,8 @@ func main() { } core.GatherTargets(sess) - GatherRepositories(sess) - AnalyzeRepositories(sess) + core.GatherRepositories(sess) + core.AnalyzeRepositories(sess) sess.Finish() if *sess.Options.Save != "" { @@ -240,7 +53,7 @@ func main() { } } - PrintSessionStats(sess) + core.PrintSessionStats(sess) sess.Out.Important("Press Ctrl+C to stop web server and exit.\n\n") select {} } From d5d83783c2f4c172cbe9cdd46299ef908b506d79 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 15:36:09 -0400 Subject: [PATCH 069/226] update gitignore configuration --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index e9bdbf3e..b5810854 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,9 @@ gitrob.exe vendor/ build/ .vscode +.idea __debug_bin +go_build_gitrob_ # Test binary, build with `go test -c` *.test From 5df2349f7593fb9cf977cb1306b8f93bf16e4734 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 15:36:21 -0400 Subject: [PATCH 070/226] =?UTF-8?q?add=20=E2=80=98mode=E2=80=99=20option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/options.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/options.go b/core/options.go index 42a7d016..542795b3 100644 --- a/core/options.go +++ b/core/options.go @@ -8,6 +8,7 @@ type Options struct { CommitDepth *int GithubAccessToken *string `json:"-"` GitLabAccessToken *string `json:"-"` + Mode *int `json:"-"` NoExpandOrgs *bool Threads *int Save *string `json:"-"` @@ -24,6 +25,7 @@ func ParseOptions() (Options, error) { CommitDepth: flag.Int("commit-depth", 500, "Number of repository commits to process"), GithubAccessToken: flag.String("github-access-token", "", "GitHub access token to use for API requests"), GitLabAccessToken: flag.String("gitlab-access-token", "", "GitLab access token to use for API requests"), + Mode: flag.Int("mode", 1, "Matching mode (see documentation)."), NoExpandOrgs: flag.Bool("no-expand-orgs", false, "Don't add members to targets when processing organizations"), Threads: flag.Int("threads", 0, "Number of concurrent threads (default number of logical CPUs)"), Save: flag.String("save", "", "Save session to file"), From 8141b4f61558cf2453cad876647fdfa14ed3bd28 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 16:06:44 -0400 Subject: [PATCH 071/226] remove unused consts --- core/signatures.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/signatures.go b/core/signatures.go index b8c2cad9..0d4e80b3 100644 --- a/core/signatures.go +++ b/core/signatures.go @@ -10,9 +10,6 @@ import ( ) const ( - TypeSimple = "simple" - TypePattern = "pattern" - PartExtension = "extension" PartFilename = "filename" PartPath = "path" From 8f8c06575e71148178ff8fa178b20aa981831a43 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 16:06:57 -0400 Subject: [PATCH 072/226] use shared method to clean strings --- core/signatures.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/signatures.go b/core/signatures.go index 0d4e80b3..7409c484 100644 --- a/core/signatures.go +++ b/core/signatures.go @@ -3,6 +3,7 @@ package core import ( "crypto/sha1" "fmt" + "github.com/codeEmitter/gitrob/common" "io" "path/filepath" "regexp" @@ -62,9 +63,8 @@ func (f *Finding) setupUrls(isGithubSession bool) { f.FileUrl = fmt.Sprintf("%s/blob/%s/%s", f.RepositoryUrl, f.CommitHash, f.FilePath) f.CommitUrl = fmt.Sprintf("%s/commit/%s", f.RepositoryUrl, f.CommitHash) } else { - repoOwner := strings.ReplaceAll(f.RepositoryOwner, " ", "-") - repoName := strings.ReplaceAll(f.RepositoryName, " ", "-") - f.RepositoryUrl = fmt.Sprintf("https://gitlab.com/%s/%s", repoOwner, repoName) + results := common.CleanUrlSpaces(f.RepositoryOwner, f.RepositoryName) + f.RepositoryUrl = fmt.Sprintf("https://gitlab.com/%s/%s", results[0], results[1]) f.FileUrl = fmt.Sprintf("%s/blob/%s/%s", f.RepositoryUrl, f.CommitHash, f.FilePath) f.CommitUrl = fmt.Sprintf("%s/commit/%s", f.RepositoryUrl, f.CommitHash) } From e0e7b4d1b57832dc4cd362df497542adad80f293 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 16:27:35 -0400 Subject: [PATCH 073/226] create a matching module. refactor file matching logic. --- core/analysis.go | 3 ++- core/signatures.go | 44 ++++------------------------------------ matching/filematching.go | 41 +++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 41 deletions(-) create mode 100644 matching/filematching.go diff --git a/core/analysis.go b/core/analysis.go index 75a955e1..7c0ab72e 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -4,6 +4,7 @@ import ( "github.com/codeEmitter/gitrob/common" "github.com/codeEmitter/gitrob/github" "github.com/codeEmitter/gitrob/gitlab" + "github.com/codeEmitter/gitrob/matching" "gopkg.in/src-d/go-git.v4" "os" "strings" @@ -162,7 +163,7 @@ func AnalyzeRepositories(sess *Session) { for _, change := range changes { changeAction := common.GetChangeAction(change) path := common.GetChangePath(change) - matchFile := NewMatchFile(path) + matchFile := matching.NewMatchFile(path) if matchFile.IsSkippable() { sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.CloneURL, matchFile.Path) continue diff --git a/core/signatures.go b/core/signatures.go index 7409c484..2498845e 100644 --- a/core/signatures.go +++ b/core/signatures.go @@ -4,10 +4,9 @@ import ( "crypto/sha1" "fmt" "github.com/codeEmitter/gitrob/common" + "github.com/codeEmitter/gitrob/matching" "io" - "path/filepath" "regexp" - "strings" ) const ( @@ -16,31 +15,6 @@ const ( PartPath = "path" ) -var skippableExtensions = []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".tif", ".psd", ".xcf"} -var skippablePathIndicators = []string{"node_modules/", "vendor/bundle", "vendor/cache"} - -type MatchFile struct { - Path string - Filename string - Extension string -} - -func (f *MatchFile) IsSkippable() bool { - ext := strings.ToLower(f.Extension) - path := strings.ToLower(f.Path) - for _, skippableExt := range skippableExtensions { - if ext == skippableExt { - return true - } - } - for _, skippablePathIndicator := range skippablePathIndicators { - if strings.Contains(path, skippablePathIndicator) { - return true - } - } - return false -} - type Finding struct { Id string FilePath string @@ -88,7 +62,7 @@ func (f *Finding) Initialize(isGithubSession bool) { } type Signature interface { - Match(file MatchFile) bool + Match(file matching.MatchFile) bool Description() string Comment() string } @@ -107,7 +81,7 @@ type PatternSignature struct { comment string } -func (s SimpleSignature) Match(file MatchFile) bool { +func (s SimpleSignature) Match(file matching.MatchFile) bool { var haystack *string switch s.part { case PartPath: @@ -131,7 +105,7 @@ func (s SimpleSignature) Comment() string { return s.comment } -func (s PatternSignature) Match(file MatchFile) bool { +func (s PatternSignature) Match(file matching.MatchFile) bool { var haystack *string switch s.part { case PartPath: @@ -155,16 +129,6 @@ func (s PatternSignature) Comment() string { return s.comment } -func NewMatchFile(path string) MatchFile { - _, filename := filepath.Split(path) - extension := filepath.Ext(path) - return MatchFile{ - Path: path, - Filename: filename, - Extension: extension, - } -} - var Signatures = []Signature{ SimpleSignature{ part: PartExtension, diff --git a/matching/filematching.go b/matching/filematching.go new file mode 100644 index 00000000..25963987 --- /dev/null +++ b/matching/filematching.go @@ -0,0 +1,41 @@ +package matching + +import ( + "path/filepath" + "strings" +) + +type MatchFile struct { + Path string + Filename string + Extension string +} + +var skippableExtensions = []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".tif", ".psd", ".xcf"} +var skippablePathIndicators = []string{"node_modules/", "vendor/bundle", "vendor/cache"} + +func (f *MatchFile) IsSkippable() bool { + ext := strings.ToLower(f.Extension) + path := strings.ToLower(f.Path) + for _, skippableExt := range skippableExtensions { + if ext == skippableExt { + return true + } + } + for _, skippablePathIndicator := range skippablePathIndicators { + if strings.Contains(path, skippablePathIndicator) { + return true + } + } + return false +} + +func NewMatchFile(path string) MatchFile { + _, filename := filepath.Split(path) + extension := filepath.Ext(path) + return MatchFile{ + Path: path, + Filename: filename, + Extension: extension, + } +} From 02a020257ef7ac0cb0cdb2dc83bea344766cecd7 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 16:54:51 -0400 Subject: [PATCH 074/226] move signature types, interfaces, and implementations into matching module --- core/analysis.go | 8 +- core/session.go | 5 +- core/signatures.go | 709 ----------------------------------- main.go | 3 +- matching/findings.go | 54 +++ matching/interfaces.go | 1 + matching/patternsignature.go | 34 ++ matching/signatures.go | 594 +++++++++++++++++++++++++++++ matching/simplesignature.go | 33 ++ 9 files changed, 725 insertions(+), 716 deletions(-) delete mode 100644 core/signatures.go create mode 100644 matching/findings.go create mode 100644 matching/interfaces.go create mode 100644 matching/patternsignature.go create mode 100644 matching/signatures.go create mode 100644 matching/simplesignature.go diff --git a/core/analysis.go b/core/analysis.go index 7c0ab72e..d185b3cb 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -169,14 +169,14 @@ func AnalyzeRepositories(sess *Session) { continue } sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.CloneURL, matchFile.Path) - for _, signature := range Signatures { + for _, signature := range matching.Signatures { if signature.Match(matchFile) { - finding := &Finding{ + finding := &matching.Finding{ FilePath: path, Action: changeAction, - Description: signature.Description(), - Comment: signature.Comment(), + Description: signature.GetDescription(), + Comment: signature.GetComment(), RepositoryOwner: *repo.Owner, RepositoryName: *repo.Name, CommitHash: commit.Hash.String(), diff --git a/core/session.go b/core/session.go index b69466d9..a875a1c1 100644 --- a/core/session.go +++ b/core/session.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/codeEmitter/gitrob/matching" "io/ioutil" "os" "runtime" @@ -60,7 +61,7 @@ type Session struct { Router *gin.Engine `json:"-"` //do not unmarshal to json on save Targets []*common.Owner Repositories []*common.Repository - Findings []*Finding + Findings []*matching.Finding } func (s *Session) Initialize() { @@ -100,7 +101,7 @@ func (s *Session) AddRepository(repository *common.Repository) { s.Repositories = append(s.Repositories, repository) } -func (s *Session) AddFinding(finding *Finding) { +func (s *Session) AddFinding(finding *matching.Finding) { s.Lock() defer s.Unlock() s.Findings = append(s.Findings, finding) diff --git a/core/signatures.go b/core/signatures.go deleted file mode 100644 index 2498845e..00000000 --- a/core/signatures.go +++ /dev/null @@ -1,709 +0,0 @@ -package core - -import ( - "crypto/sha1" - "fmt" - "github.com/codeEmitter/gitrob/common" - "github.com/codeEmitter/gitrob/matching" - "io" - "regexp" -) - -const ( - PartExtension = "extension" - PartFilename = "filename" - PartPath = "path" -) - -type Finding struct { - Id string - FilePath string - Action string - Description string - Comment string - RepositoryOwner string - RepositoryName string - CommitHash string - CommitMessage string - CommitAuthor string - FileUrl string - CommitUrl string - RepositoryUrl string -} - -func (f *Finding) setupUrls(isGithubSession bool) { - if isGithubSession { - f.RepositoryUrl = fmt.Sprintf("https://github.com/%s/%s", f.RepositoryOwner, f.RepositoryName) - f.FileUrl = fmt.Sprintf("%s/blob/%s/%s", f.RepositoryUrl, f.CommitHash, f.FilePath) - f.CommitUrl = fmt.Sprintf("%s/commit/%s", f.RepositoryUrl, f.CommitHash) - } else { - results := common.CleanUrlSpaces(f.RepositoryOwner, f.RepositoryName) - f.RepositoryUrl = fmt.Sprintf("https://gitlab.com/%s/%s", results[0], results[1]) - f.FileUrl = fmt.Sprintf("%s/blob/%s/%s", f.RepositoryUrl, f.CommitHash, f.FilePath) - f.CommitUrl = fmt.Sprintf("%s/commit/%s", f.RepositoryUrl, f.CommitHash) - } -} - -func (f *Finding) generateID() { - h := sha1.New() - io.WriteString(h, f.FilePath) - io.WriteString(h, f.Action) - io.WriteString(h, f.RepositoryOwner) - io.WriteString(h, f.RepositoryName) - io.WriteString(h, f.CommitHash) - io.WriteString(h, f.CommitMessage) - io.WriteString(h, f.CommitAuthor) - f.Id = fmt.Sprintf("%x", h.Sum(nil)) -} - -func (f *Finding) Initialize(isGithubSession bool) { - f.setupUrls(isGithubSession) - f.generateID() -} - -type Signature interface { - Match(file matching.MatchFile) bool - Description() string - Comment() string -} - -type SimpleSignature struct { - part string - match string - description string - comment string -} - -type PatternSignature struct { - part string - match *regexp.Regexp - description string - comment string -} - -func (s SimpleSignature) Match(file matching.MatchFile) bool { - var haystack *string - switch s.part { - case PartPath: - haystack = &file.Path - case PartFilename: - haystack = &file.Filename - case PartExtension: - haystack = &file.Extension - default: - return false - } - - return (s.match == *haystack) -} - -func (s SimpleSignature) Description() string { - return s.description -} - -func (s SimpleSignature) Comment() string { - return s.comment -} - -func (s PatternSignature) Match(file matching.MatchFile) bool { - var haystack *string - switch s.part { - case PartPath: - haystack = &file.Path - case PartFilename: - haystack = &file.Filename - case PartExtension: - haystack = &file.Extension - default: - return false - } - - return s.match.MatchString(*haystack) -} - -func (s PatternSignature) Description() string { - return s.description -} - -func (s PatternSignature) Comment() string { - return s.comment -} - -var Signatures = []Signature{ - SimpleSignature{ - part: PartExtension, - match: ".pem", - description: "Potential cryptographic private key", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".log", - description: "Log file", - comment: "Log files can contain secret HTTP endpoints, session IDs, API keys and other goodies", - }, - SimpleSignature{ - part: PartExtension, - match: ".pkcs12", - description: "Potential cryptographic key bundle", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".p12", - description: "Potential cryptographic key bundle", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".pfx", - description: "Potential cryptographic key bundle", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".asc", - description: "Potential cryptographic key bundle", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "otr.private_key", - description: "Pidgin OTR private key", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".ovpn", - description: "OpenVPN client configuration file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".cscfg", - description: "Azure service configuration schema file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".rdp", - description: "Remote Desktop connection file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".mdf", - description: "Microsoft SQL database file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".sdf", - description: "Microsoft SQL server compact database file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".sqlite", - description: "SQLite database file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".bek", - description: "Microsoft BitLocker recovery key file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".tpm", - description: "Microsoft BitLocker Trusted Platform Module password file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".fve", - description: "Windows BitLocker full volume encrypted data file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".jks", - description: "Java keystore file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".psafe3", - description: "Password Safe database file", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "secret_token.rb", - description: "Ruby On Rails secret token configuration file", - comment: "If the Rails secret token is known, it can allow for remote code execution (http://www.exploit-db.com/exploits/27527/)", - }, - SimpleSignature{ - part: PartFilename, - match: "carrierwave.rb", - description: "Carrierwave configuration file", - comment: "Can contain credentials for cloud storage systems such as Amazon S3 and Google Storage", - }, - SimpleSignature{ - part: PartFilename, - match: "database.yml", - description: "Potential Ruby On Rails database configuration file", - comment: "Can contain database credentials", - }, - SimpleSignature{ - part: PartFilename, - match: "omniauth.rb", - description: "OmniAuth configuration file", - comment: "The OmniAuth configuration file can contain client application secrets", - }, - SimpleSignature{ - part: PartFilename, - match: "settings.py", - description: "Django configuration file", - comment: "Can contain database credentials, cloud storage system credentials, and other secrets", - }, - SimpleSignature{ - part: PartExtension, - match: ".agilekeychain", - description: "1Password password manager database file", - comment: "Feed it to Hashcat and see if you're lucky", - }, - SimpleSignature{ - part: PartExtension, - match: ".keychain", - description: "Apple Keychain database file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".pcap", - description: "Network traffic capture file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".gnucash", - description: "GnuCash database file", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin.xml", - description: "Jenkins publish over SSH plugin file", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "credentials.xml", - description: "Potential Jenkins credentials file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".kwallet", - description: "KDE Wallet Manager database file", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "LocalSettings.php", - description: "Potential MediaWiki configuration file", - comment: "", - }, - SimpleSignature{ - part: PartExtension, - match: ".tblk", - description: "Tunnelblick VPN configuration file", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "Favorites.plist", - description: "Sequel Pro MySQL database manager bookmark file", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "configuration.user.xpl", - description: "Little Snitch firewall configuration file", - comment: "Contains traffic rules for applications", - }, - SimpleSignature{ - part: PartExtension, - match: ".dayone", - description: "Day One journal file", - comment: "Now it's getting creepy...", - }, - SimpleSignature{ - part: PartFilename, - match: "journal.txt", - description: "Potential jrnl journal file", - comment: "Now it's getting creepy...", - }, - SimpleSignature{ - part: PartFilename, - match: "knife.rb", - description: "Chef Knife configuration file", - comment: "Can contain references to Chef servers", - }, - SimpleSignature{ - part: PartFilename, - match: "proftpdpasswd", - description: "cPanel backup ProFTPd credentials file", - comment: "Contains usernames and password hashes for FTP accounts", - }, - SimpleSignature{ - part: PartFilename, - match: "robomongo.json", - description: "Robomongo MongoDB manager configuration file", - comment: "Can contain credentials for MongoDB databases", - }, - SimpleSignature{ - part: PartFilename, - match: "filezilla.xml", - description: "FileZilla FTP configuration file", - comment: "Can contain credentials for FTP servers", - }, - SimpleSignature{ - part: PartFilename, - match: "recentservers.xml", - description: "FileZilla FTP recent servers file", - comment: "Can contain credentials for FTP servers", - }, - SimpleSignature{ - part: PartFilename, - match: "ventrilo_srv.ini", - description: "Ventrilo server configuration file", - comment: "Can contain passwords", - }, - SimpleSignature{ - part: PartFilename, - match: "terraform.tfvars", - description: "Terraform variable config file", - comment: "Can contain credentials for terraform providers", - }, - SimpleSignature{ - part: PartFilename, - match: ".exports", - description: "Shell configuration file", - comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - SimpleSignature{ - part: PartFilename, - match: ".functions", - description: "Shell configuration file", - comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - SimpleSignature{ - part: PartFilename, - match: ".extra", - description: "Shell configuration file", - comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^.*_rsa$`), - description: "Private SSH key", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^.*_dsa$`), - description: "Private SSH key", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^.*_ed25519$`), - description: "Private SSH key", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^.*_ecdsa$`), - description: "Private SSH key", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?ssh/config$`), - description: "SSH configuration file", - comment: "", - }, - PatternSignature{ - part: PartExtension, - match: regexp.MustCompile(`^key(pair)?$`), - description: "Potential cryptographic private key", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?(bash_|zsh_|sh_|z)?history$`), - description: "Shell command history file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?mysql_history$`), - description: "MySQL client command history file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?psql_history$`), - description: "PostgreSQL client command history file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?pgpass$`), - description: "PostgreSQL password file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?irb_history$`), - description: "Ruby IRB console history file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?purple/accounts\.xml$`), - description: "Pidgin chat client account configuration file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?xchat2?/servlist_?\.conf$`), - description: "Hexchat/XChat IRC client server list configuration file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?irssi/config$`), - description: "Irssi IRC client configuration file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?recon-ng/keys\.db$`), - description: "Recon-ng web reconnaissance framework API key database", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?dbeaver-data-sources.xml$`), - description: "DBeaver SQL database manager configuration file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?muttrc$`), - description: "Mutt e-mail client configuration file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?s3cfg$`), - description: "S3cmd configuration file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?aws/credentials$`), - description: "AWS CLI credentials file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^sftp-config(\.json)?$`), - description: "SFTP connection configuration file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?trc$`), - description: "T command-line Twitter client configuration file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?gitrobrc$`), - description: "Well, this is awkward... Gitrob configuration file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?(bash|zsh|csh)rc$`), - description: "Shell configuration file", - comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?(bash_|zsh_)?profile$`), - description: "Shell profile configuration file", - comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?(bash_|zsh_)?aliases$`), - description: "Shell command alias configuration file", - comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`config(\.inc)?\.php$`), - description: "PHP configuration file", - comment: "", - }, - PatternSignature{ - part: PartExtension, - match: regexp.MustCompile(`^key(store|ring)$`), - description: "GNOME Keyring database file", - comment: "", - }, - PatternSignature{ - part: PartExtension, - match: regexp.MustCompile(`^kdbx?$`), - description: "KeePass password manager database file", - comment: "Feed it to Hashcat and see if you're lucky", - }, - PatternSignature{ - part: PartExtension, - match: regexp.MustCompile(`^sql(dump)?$`), - description: "SQL dump file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?htpasswd$`), - description: "Apache htpasswd file", - comment: "", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^(\.|_)?netrc$`), - description: "Configuration file for auto-login process", - comment: "Can contain username and password", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?gem/credentials$`), - description: "Rubygems credentials file", - comment: "Can contain API key for a rubygems.org account", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?tugboat$`), - description: "Tugboat DigitalOcean management tool configuration", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`doctl/config.yaml$`), - description: "DigitalOcean doctl command-line client configuration file", - comment: "Contains DigitalOcean API key and other information", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?git-credentials$`), - description: "git-credential-store helper credentials file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`config/hub$`), - description: "GitHub Hub command-line client configuration file", - comment: "Can contain GitHub API access token", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?gitconfig$`), - description: "Git configuration file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`\.?chef/(.*)\.pem$`), - description: "Chef private key", - comment: "Can be used to authenticate against Chef servers", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`etc/shadow$`), - description: "Potential Linux shadow file", - comment: "Contains hashed passwords for system users", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`etc/passwd$`), - description: "Potential Linux passwd file", - comment: "Contains system user information", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?dockercfg$`), - description: "Docker configuration file", - comment: "Can contain credentials for public or private Docker registries", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?npmrc$`), - description: "NPM configuration file", - comment: "Can contain credentials for NPM registries", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^\.?env$`), - description: "Environment configuration file", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`credential`), - description: "Contains word: credential", - comment: "", - }, - PatternSignature{ - part: PartPath, - match: regexp.MustCompile(`password`), - description: "Contains word: password", - comment: "", - }, - SimpleSignature{ - part: PartFilename, - match: "credentials.json", - description: "Google Cloud Platform service account credentials keyfile", - comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", - }, - PatternSignature{ - part: PartFilename, - match: regexp.MustCompile(`^.*-[a-f0-9]{12}\.json$`), - description: "Google Cloud Platform service account credentials keyfile", - comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", - }, - SimpleSignature{ - part: PartFilename, - match: "adc.json", - description: "Legacy Google Cloud Platform service account credentials keyfile", - comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", - }, - SimpleSignature{ - part: PartFilename, - match: "credentials.db", - description: "Google Cloud Platform gcloud credential database", - comment: "sqlite database containing credentials used by the gcloud command from Google's Cloud SDK", - }, - SimpleSignature{ - part: PartFilename, - match: ".boto", - description: "Legacy Google Cloud Platform gcloud credential database", - comment: "File containing credentials used by the gcloud command from Google's Cloud SDK", - }, -} diff --git a/main.go b/main.go index 9dda025a..b1e4fc9a 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/codeEmitter/gitrob/matching" "os" "time" @@ -22,7 +23,7 @@ func main() { sess.Out.Info("%s\n\n", common.ASCIIBanner) sess.Out.Important("%s v%s started at %s\n", common.Name, common.Version, sess.Stats.StartedAt.Format(time.RFC3339)) - sess.Out.Important("Loaded %d signatures\n", len(core.Signatures)) + sess.Out.Important("Loaded %d signatures\n", len(matching.Signatures)) sess.Out.Important("Web interface available at http://%s:%d\n", *sess.Options.BindAddress, *sess.Options.Port) if sess.Stats.Status == "finished" { diff --git a/matching/findings.go b/matching/findings.go new file mode 100644 index 00000000..ea39c14c --- /dev/null +++ b/matching/findings.go @@ -0,0 +1,54 @@ +package matching + +import ( + "crypto/sha1" + "fmt" + "github.com/codeEmitter/gitrob/common" + "io" +) + +type Finding struct { + Id string + FilePath string + Action string + Description string + Comment string + RepositoryOwner string + RepositoryName string + CommitHash string + CommitMessage string + CommitAuthor string + FileUrl string + CommitUrl string + RepositoryUrl string +} + +func (f *Finding) setupUrls(isGithubSession bool) { + if isGithubSession { + f.RepositoryUrl = fmt.Sprintf("https://github.com/%s/%s", f.RepositoryOwner, f.RepositoryName) + f.FileUrl = fmt.Sprintf("%s/blob/%s/%s", f.RepositoryUrl, f.CommitHash, f.FilePath) + f.CommitUrl = fmt.Sprintf("%s/commit/%s", f.RepositoryUrl, f.CommitHash) + } else { + results := common.CleanUrlSpaces(f.RepositoryOwner, f.RepositoryName) + f.RepositoryUrl = fmt.Sprintf("https://gitlab.com/%s/%s", results[0], results[1]) + f.FileUrl = fmt.Sprintf("%s/blob/%s/%s", f.RepositoryUrl, f.CommitHash, f.FilePath) + f.CommitUrl = fmt.Sprintf("%s/commit/%s", f.RepositoryUrl, f.CommitHash) + } +} + +func (f *Finding) generateID() { + h := sha1.New() + io.WriteString(h, f.FilePath) + io.WriteString(h, f.Action) + io.WriteString(h, f.RepositoryOwner) + io.WriteString(h, f.RepositoryName) + io.WriteString(h, f.CommitHash) + io.WriteString(h, f.CommitMessage) + io.WriteString(h, f.CommitAuthor) + f.Id = fmt.Sprintf("%x", h.Sum(nil)) +} + +func (f *Finding) Initialize(isGithubSession bool) { + f.setupUrls(isGithubSession) + f.generateID() +} \ No newline at end of file diff --git a/matching/interfaces.go b/matching/interfaces.go new file mode 100644 index 00000000..af90ad1c --- /dev/null +++ b/matching/interfaces.go @@ -0,0 +1 @@ +package matching diff --git a/matching/patternsignature.go b/matching/patternsignature.go new file mode 100644 index 00000000..0d5fa389 --- /dev/null +++ b/matching/patternsignature.go @@ -0,0 +1,34 @@ +package matching + +import "regexp" + +type PatternSignature struct { + Part string + MatchOn *regexp.Regexp + Description string + Comment string +} + +func (s PatternSignature) Match(file MatchFile) bool { + var haystack *string + switch s.Part { + case PartPath: + haystack = &file.Path + case PartFilename: + haystack = &file.Filename + case PartExtension: + haystack = &file.Extension + default: + return false + } + + return s.MatchOn.MatchString(*haystack) +} + +func (s PatternSignature) GetDescription() string { + return s.Description +} + +func (s PatternSignature) GetComment() string { + return s.Comment +} diff --git a/matching/signatures.go b/matching/signatures.go new file mode 100644 index 00000000..94800e18 --- /dev/null +++ b/matching/signatures.go @@ -0,0 +1,594 @@ +package matching + +import "regexp" + +const ( + PartExtension = "extension" + PartFilename = "filename" + PartPath = "path" +) + +type Signature interface { + Match(file MatchFile) bool + GetDescription() string + GetComment() string +} + +var Signatures = []Signature{ + SimpleSignature{ + Part: PartExtension, + MatchOn: ".pem", + Description: "Potential cryptographic private key", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".log", + Description: "Log file", + Comment: "Log files can contain secret HTTP endpoints, session IDs, API keys and other goodies", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".pkcs12", + Description: "Potential cryptographic key bundle", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".p12", + Description: "Potential cryptographic key bundle", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".pfx", + Description: "Potential cryptographic key bundle", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".asc", + Description: "Potential cryptographic key bundle", + Comment: "", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "otr.private_key", + Description: "Pidgin OTR private key", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".ovpn", + Description: "OpenVPN client configuration file", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".cscfg", + Description: "Azure service configuration schema file", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".rdp", + Description: "Remote Desktop connection file", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".mdf", + Description: "Microsoft SQL database file", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".sdf", + Description: "Microsoft SQL server compact database file", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".sqlite", + Description: "SQLite database file", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".bek", + Description: "Microsoft BitLocker recovery key file", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".tpm", + Description: "Microsoft BitLocker Trusted Platform Module password file", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".fve", + Description: "Windows BitLocker full volume encrypted data file", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".jks", + Description: "Java keystore file", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".psafe3", + Description: "Password Safe database file", + Comment: "", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "secret_token.rb", + Description: "Ruby On Rails secret token configuration file", + Comment: "If the Rails secret token is known, it can allow for remote code execution (http://www.exploit-db.com/exploits/27527/)", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "carrierwave.rb", + Description: "Carrierwave configuration file", + Comment: "Can contain credentials for cloud storage systems such as Amazon S3 and Google Storage", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "database.yml", + Description: "Potential Ruby On Rails database configuration file", + Comment: "Can contain database credentials", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "omniauth.rb", + Description: "OmniAuth configuration file", + Comment: "The OmniAuth configuration file can contain client application secrets", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "settings.py", + Description: "Django configuration file", + Comment: "Can contain database credentials, cloud storage system credentials, and other secrets", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".agilekeychain", + Description: "1Password password manager database file", + Comment: "Feed it to Hashcat and see if you're lucky", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".keychain", + Description: "Apple Keychain database file", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".pcap", + Description: "Network traffic capture file", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".gnucash", + Description: "GnuCash database file", + Comment: "", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin.xml", + Description: "Jenkins publish over SSH plugin file", + Comment: "", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "credentials.xml", + Description: "Potential Jenkins credentials file", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".kwallet", + Description: "KDE Wallet Manager database file", + Comment: "", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "LocalSettings.php", + Description: "Potential MediaWiki configuration file", + Comment: "", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".tblk", + Description: "Tunnelblick VPN configuration file", + Comment: "", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "Favorites.plist", + Description: "Sequel Pro MySQL database manager bookmark file", + Comment: "", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "configuration.user.xpl", + Description: "Little Snitch firewall configuration file", + Comment: "Contains traffic rules for applications", + }, + SimpleSignature{ + Part: PartExtension, + MatchOn: ".dayone", + Description: "Day One journal file", + Comment: "Now it's getting creepy...", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "journal.txt", + Description: "Potential jrnl journal file", + Comment: "Now it's getting creepy...", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "knife.rb", + Description: "Chef Knife configuration file", + Comment: "Can contain references to Chef servers", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "proftpdpasswd", + Description: "cPanel backup ProFTPd credentials file", + Comment: "Contains usernames and password hashes for FTP accounts", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "robomongo.json", + Description: "Robomongo MongoDB manager configuration file", + Comment: "Can contain credentials for MongoDB databases", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "filezilla.xml", + Description: "FileZilla FTP configuration file", + Comment: "Can contain credentials for FTP servers", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "recentservers.xml", + Description: "FileZilla FTP recent servers file", + Comment: "Can contain credentials for FTP servers", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "ventrilo_srv.ini", + Description: "Ventrilo server configuration file", + Comment: "Can contain passwords", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "terraform.tfvars", + Description: "Terraform variable config file", + Comment: "Can contain credentials for terraform providers", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: ".exports", + Description: "Shell configuration file", + Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: ".functions", + Description: "Shell configuration file", + Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: ".extra", + Description: "Shell configuration file", + Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^.*_rsa$`), + Description: "Private SSH key", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^.*_dsa$`), + Description: "Private SSH key", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^.*_ed25519$`), + Description: "Private SSH key", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^.*_ecdsa$`), + Description: "Private SSH key", + Comment: "", + }, + PatternSignature{ + Part: PartPath, + MatchOn: regexp.MustCompile(`\.?ssh/config$`), + Description: "SSH configuration file", + Comment: "", + }, + PatternSignature{ + Part: PartExtension, + MatchOn: regexp.MustCompile(`^key(pair)?$`), + Description: "Potential cryptographic private key", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_|sh_|z)?history$`), + Description: "Shell command history file", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?mysql_history$`), + Description: "MySQL client command history file", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?psql_history$`), + Description: "PostgreSQL client command history file", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?pgpass$`), + Description: "PostgreSQL password file", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?irb_history$`), + Description: "Ruby IRB console history file", + Comment: "", + }, + PatternSignature{ + Part: PartPath, + MatchOn: regexp.MustCompile(`\.?purple/accounts\.xml$`), + Description: "Pidgin chat client account configuration file", + Comment: "", + }, + PatternSignature{ + Part: PartPath, + MatchOn: regexp.MustCompile(`\.?xchat2?/servlist_?\.conf$`), + Description: "Hexchat/XChat IRC client server list configuration file", + Comment: "", + }, + PatternSignature{ + Part: PartPath, + MatchOn: regexp.MustCompile(`\.?irssi/config$`), + Description: "Irssi IRC client configuration file", + Comment: "", + }, + PatternSignature{ + Part: PartPath, + MatchOn: regexp.MustCompile(`\.?recon-ng/keys\.db$`), + Description: "Recon-ng web reconnaissance framework API key database", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?dbeaver-data-sources.xml$`), + Description: "DBeaver SQL database manager configuration file", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?muttrc$`), + Description: "Mutt e-mail client configuration file", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?s3cfg$`), + Description: "S3cmd configuration file", + Comment: "", + }, + PatternSignature{ + Part: PartPath, + MatchOn: regexp.MustCompile(`\.?aws/credentials$`), + Description: "AWS CLI credentials file", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^sftp-config(\.json)?$`), + Description: "SFTP connection configuration file", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?trc$`), + Description: "T command-line Twitter client configuration file", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?gitrobrc$`), + Description: "Well, this is awkward... Gitrob configuration file", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?(bash|zsh|csh)rc$`), + Description: "Shell configuration file", + Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_)?profile$`), + Description: "Shell profile configuration file", + Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_)?aliases$`), + Description: "Shell command alias configuration file", + Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`config(\.inc)?\.php$`), + Description: "PHP configuration file", + Comment: "", + }, + PatternSignature{ + Part: PartExtension, + MatchOn: regexp.MustCompile(`^key(store|ring)$`), + Description: "GNOME Keyring database file", + Comment: "", + }, + PatternSignature{ + Part: PartExtension, + MatchOn: regexp.MustCompile(`^kdbx?$`), + Description: "KeePass password manager database file", + Comment: "Feed it to Hashcat and see if you're lucky", + }, + PatternSignature{ + Part: PartExtension, + MatchOn: regexp.MustCompile(`^sql(dump)?$`), + Description: "SQL dump file", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?htpasswd$`), + Description: "Apache htpasswd file", + Comment: "", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^(\.|_)?netrc$`), + Description: "Configuration file for auto-login process", + Comment: "Can contain username and password", + }, + PatternSignature{ + Part: PartPath, + MatchOn: regexp.MustCompile(`\.?gem/credentials$`), + Description: "Rubygems credentials file", + Comment: "Can contain API key for a rubygems.org account", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?tugboat$`), + Description: "Tugboat DigitalOcean management tool configuration", + Comment: "", + }, + PatternSignature{ + Part: PartPath, + MatchOn: regexp.MustCompile(`doctl/config.yaml$`), + Description: "DigitalOcean doctl command-line client configuration file", + Comment: "Contains DigitalOcean API key and other information", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?git-credentials$`), + Description: "git-credential-store helper credentials file", + Comment: "", + }, + PatternSignature{ + Part: PartPath, + MatchOn: regexp.MustCompile(`config/hub$`), + Description: "GitHub Hub command-line client configuration file", + Comment: "Can contain GitHub API access token", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?gitconfig$`), + Description: "Git configuration file", + Comment: "", + }, + PatternSignature{ + Part: PartPath, + MatchOn: regexp.MustCompile(`\.?chef/(.*)\.pem$`), + Description: "Chef private key", + Comment: "Can be used to authenticate against Chef servers", + }, + PatternSignature{ + Part: PartPath, + MatchOn: regexp.MustCompile(`etc/shadow$`), + Description: "Potential Linux shadow file", + Comment: "Contains hashed passwords for system users", + }, + PatternSignature{ + Part: PartPath, + MatchOn: regexp.MustCompile(`etc/passwd$`), + Description: "Potential Linux passwd file", + Comment: "Contains system user information", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?dockercfg$`), + Description: "Docker configuration file", + Comment: "Can contain credentials for public or private Docker registries", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?npmrc$`), + Description: "NPM configuration file", + Comment: "Can contain credentials for NPM registries", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^\.?env$`), + Description: "Environment configuration file", + Comment: "", + }, + PatternSignature{ + Part: PartPath, + MatchOn: regexp.MustCompile(`credential`), + Description: "Contains word: credential", + Comment: "", + }, + PatternSignature{ + Part: PartPath, + MatchOn: regexp.MustCompile(`password`), + Description: "Contains word: password", + Comment: "", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "credentials.json", + Description: "Google Cloud Platform service account credentials keyfile", + Comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", + }, + PatternSignature{ + Part: PartFilename, + MatchOn: regexp.MustCompile(`^.*-[a-f0-9]{12}\.json$`), + Description: "Google Cloud Platform service account credentials keyfile", + Comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "adc.json", + Description: "Legacy Google Cloud Platform service account credentials keyfile", + Comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: "credentials.db", + Description: "Google Cloud Platform gcloud credential database", + Comment: "sqlite database containing credentials used by the gcloud command from Google's Cloud SDK", + }, + SimpleSignature{ + Part: PartFilename, + MatchOn: ".boto", + Description: "Legacy Google Cloud Platform gcloud credential database", + Comment: "File containing credentials used by the gcloud command from Google's Cloud SDK", + }, +} \ No newline at end of file diff --git a/matching/simplesignature.go b/matching/simplesignature.go new file mode 100644 index 00000000..025649c1 --- /dev/null +++ b/matching/simplesignature.go @@ -0,0 +1,33 @@ +package matching + + +type SimpleSignature struct { + Part string + MatchOn string + Description string + Comment string +} + +func (s SimpleSignature) Match(file MatchFile) bool { + var haystack *string + switch s.Part { + case PartPath: + haystack = &file.Path + case PartFilename: + haystack = &file.Filename + case PartExtension: + haystack = &file.Extension + default: + return false + } + + return s.MatchOn == *haystack +} + +func (s SimpleSignature) GetDescription() string { + return s.Description +} + +func (s SimpleSignature) GetComment() string { + return s.Comment +} From 040930cde8701f706769781bd5744a5ac429571a Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 16:55:32 -0400 Subject: [PATCH 075/226] update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b5810854..07c1e21f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ build/ .idea __debug_bin go_build_gitrob_ +gitrob-script.sh # Test binary, build with `go test -c` *.test From 11425bd1b3ce532f6415e3b94b5cacdea1442683 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 17:01:16 -0400 Subject: [PATCH 076/226] formatting only --- core/analysis.go | 3 +-- core/options.go | 4 ++-- matching/findings.go | 2 +- matching/patternsignature.go | 2 +- matching/simplesignature.go | 3 +-- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index d185b3cb..094fb50b 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -11,7 +11,6 @@ import ( "sync" ) - func PrintSessionStats(sess *Session) { sess.Out.Info("\nFindings....: %d\n", sess.Stats.Findings) sess.Out.Info("Files.......: %d\n", sess.Stats.Files) @@ -219,4 +218,4 @@ func AnalyzeRepositories(sess *Session) { } close(ch) wg.Wait() -} \ No newline at end of file +} diff --git a/core/options.go b/core/options.go index 542795b3..882cfffc 100644 --- a/core/options.go +++ b/core/options.go @@ -8,7 +8,7 @@ type Options struct { CommitDepth *int GithubAccessToken *string `json:"-"` GitLabAccessToken *string `json:"-"` - Mode *int `json:"-"` + Mode *int `json:"-"` NoExpandOrgs *bool Threads *int Save *string `json:"-"` @@ -25,7 +25,7 @@ func ParseOptions() (Options, error) { CommitDepth: flag.Int("commit-depth", 500, "Number of repository commits to process"), GithubAccessToken: flag.String("github-access-token", "", "GitHub access token to use for API requests"), GitLabAccessToken: flag.String("gitlab-access-token", "", "GitLab access token to use for API requests"), - Mode: flag.Int("mode", 1, "Matching mode (see documentation)."), + Mode: flag.Int("mode", 1, "Matching mode (see documentation)."), NoExpandOrgs: flag.Bool("no-expand-orgs", false, "Don't add members to targets when processing organizations"), Threads: flag.Int("threads", 0, "Number of concurrent threads (default number of logical CPUs)"), Save: flag.String("save", "", "Save session to file"), diff --git a/matching/findings.go b/matching/findings.go index ea39c14c..f4c8627f 100644 --- a/matching/findings.go +++ b/matching/findings.go @@ -51,4 +51,4 @@ func (f *Finding) generateID() { func (f *Finding) Initialize(isGithubSession bool) { f.setupUrls(isGithubSession) f.generateID() -} \ No newline at end of file +} diff --git a/matching/patternsignature.go b/matching/patternsignature.go index 0d5fa389..465b1f4e 100644 --- a/matching/patternsignature.go +++ b/matching/patternsignature.go @@ -4,7 +4,7 @@ import "regexp" type PatternSignature struct { Part string - MatchOn *regexp.Regexp + MatchOn *regexp.Regexp Description string Comment string } diff --git a/matching/simplesignature.go b/matching/simplesignature.go index 025649c1..6baab34b 100644 --- a/matching/simplesignature.go +++ b/matching/simplesignature.go @@ -1,9 +1,8 @@ package matching - type SimpleSignature struct { Part string - MatchOn string + MatchOn string Description string Comment string } From 1b92e113bdfc9dbdff29932a057283b43251f095 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 17:01:26 -0400 Subject: [PATCH 077/226] extract interface --- matching/interfaces.go | 6 ++++++ matching/signatures.go | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/matching/interfaces.go b/matching/interfaces.go index af90ad1c..a8281a5e 100644 --- a/matching/interfaces.go +++ b/matching/interfaces.go @@ -1 +1,7 @@ package matching + +type Signature interface { + Match(file MatchFile) bool + GetDescription() string + GetComment() string +} diff --git a/matching/signatures.go b/matching/signatures.go index 94800e18..a099a511 100644 --- a/matching/signatures.go +++ b/matching/signatures.go @@ -8,12 +8,6 @@ const ( PartPath = "path" ) -type Signature interface { - Match(file MatchFile) bool - GetDescription() string - GetComment() string -} - var Signatures = []Signature{ SimpleSignature{ Part: PartExtension, From 4a375d5047a1ae6ef73b13872364d7f689a129c4 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 17:30:10 -0400 Subject: [PATCH 078/226] create a type for file signatures --- matching/patternsignature.go | 6 +- matching/signatures.go | 402 ++++++++++++++++++----------------- matching/simplesignature.go | 6 +- 3 files changed, 210 insertions(+), 204 deletions(-) diff --git a/matching/patternsignature.go b/matching/patternsignature.go index 465b1f4e..abc8367b 100644 --- a/matching/patternsignature.go +++ b/matching/patternsignature.go @@ -12,11 +12,11 @@ type PatternSignature struct { func (s PatternSignature) Match(file MatchFile) bool { var haystack *string switch s.Part { - case PartPath: + case fileSignatureTypes.Path: haystack = &file.Path - case PartFilename: + case fileSignatureTypes.Filename: haystack = &file.Filename - case PartExtension: + case fileSignatureTypes.Extension: haystack = &file.Extension default: return false diff --git a/matching/signatures.go b/matching/signatures.go index a099a511..3765df82 100644 --- a/matching/signatures.go +++ b/matching/signatures.go @@ -2,587 +2,593 @@ package matching import "regexp" -const ( - PartExtension = "extension" - PartFilename = "filename" - PartPath = "path" -) +type FileSignatureType struct { + Extension string + Filename string + Path string +} + +var fileSignatureTypes = FileSignatureType{ + Extension: "extension", + Filename: "filename", + Path: "path", +} var Signatures = []Signature{ SimpleSignature{ - Part: PartExtension, - MatchOn: ".pem", + Part: fileSignatureTypes.Extension, + MatchOn: ".pem", Description: "Potential cryptographic private key", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".log", + Part: fileSignatureTypes.Extension, + MatchOn: ".log", Description: "Log file", Comment: "Log files can contain secret HTTP endpoints, session IDs, API keys and other goodies", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".pkcs12", + Part: fileSignatureTypes.Extension, + MatchOn: ".pkcs12", Description: "Potential cryptographic key bundle", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".p12", + Part: fileSignatureTypes.Extension, + MatchOn: ".p12", Description: "Potential cryptographic key bundle", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".pfx", + Part: fileSignatureTypes.Extension, + MatchOn: ".pfx", Description: "Potential cryptographic key bundle", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".asc", + Part: fileSignatureTypes.Extension, + MatchOn: ".asc", Description: "Potential cryptographic key bundle", Comment: "", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "otr.private_key", + Part: fileSignatureTypes.Filename, + MatchOn: "otr.private_key", Description: "Pidgin OTR private key", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".ovpn", + Part: fileSignatureTypes.Extension, + MatchOn: ".ovpn", Description: "OpenVPN client configuration file", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".cscfg", + Part: fileSignatureTypes.Extension, + MatchOn: ".cscfg", Description: "Azure service configuration schema file", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".rdp", + Part: fileSignatureTypes.Extension, + MatchOn: ".rdp", Description: "Remote Desktop connection file", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".mdf", + Part: fileSignatureTypes.Extension, + MatchOn: ".mdf", Description: "Microsoft SQL database file", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".sdf", + Part: fileSignatureTypes.Extension, + MatchOn: ".sdf", Description: "Microsoft SQL server compact database file", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".sqlite", + Part: fileSignatureTypes.Extension, + MatchOn: ".sqlite", Description: "SQLite database file", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".bek", + Part: fileSignatureTypes.Extension, + MatchOn: ".bek", Description: "Microsoft BitLocker recovery key file", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".tpm", + Part: fileSignatureTypes.Extension, + MatchOn: ".tpm", Description: "Microsoft BitLocker Trusted Platform Module password file", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".fve", + Part: fileSignatureTypes.Extension, + MatchOn: ".fve", Description: "Windows BitLocker full volume encrypted data file", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".jks", + Part: fileSignatureTypes.Extension, + MatchOn: ".jks", Description: "Java keystore file", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".psafe3", + Part: fileSignatureTypes.Extension, + MatchOn: ".psafe3", Description: "Password Safe database file", Comment: "", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "secret_token.rb", + Part: fileSignatureTypes.Filename, + MatchOn: "secret_token.rb", Description: "Ruby On Rails secret token configuration file", Comment: "If the Rails secret token is known, it can allow for remote code execution (http://www.exploit-db.com/exploits/27527/)", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "carrierwave.rb", + Part: fileSignatureTypes.Filename, + MatchOn: "carrierwave.rb", Description: "Carrierwave configuration file", Comment: "Can contain credentials for cloud storage systems such as Amazon S3 and Google Storage", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "database.yml", + Part: fileSignatureTypes.Filename, + MatchOn: "database.yml", Description: "Potential Ruby On Rails database configuration file", Comment: "Can contain database credentials", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "omniauth.rb", + Part: fileSignatureTypes.Filename, + MatchOn: "omniauth.rb", Description: "OmniAuth configuration file", Comment: "The OmniAuth configuration file can contain client application secrets", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "settings.py", + Part: fileSignatureTypes.Filename, + MatchOn: "settings.py", Description: "Django configuration file", Comment: "Can contain database credentials, cloud storage system credentials, and other secrets", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".agilekeychain", + Part: fileSignatureTypes.Extension, + MatchOn: ".agilekeychain", Description: "1Password password manager database file", Comment: "Feed it to Hashcat and see if you're lucky", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".keychain", + Part: fileSignatureTypes.Extension, + MatchOn: ".keychain", Description: "Apple Keychain database file", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".pcap", + Part: fileSignatureTypes.Extension, + MatchOn: ".pcap", Description: "Network traffic capture file", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".gnucash", + Part: fileSignatureTypes.Extension, + MatchOn: ".gnucash", Description: "GnuCash database file", Comment: "", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin.xml", + Part: fileSignatureTypes.Filename, + MatchOn: "jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin.xml", Description: "Jenkins publish over SSH plugin file", Comment: "", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "credentials.xml", + Part: fileSignatureTypes.Filename, + MatchOn: "credentials.xml", Description: "Potential Jenkins credentials file", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".kwallet", + Part: fileSignatureTypes.Extension, + MatchOn: ".kwallet", Description: "KDE Wallet Manager database file", Comment: "", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "LocalSettings.php", + Part: fileSignatureTypes.Filename, + MatchOn: "LocalSettings.php", Description: "Potential MediaWiki configuration file", Comment: "", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".tblk", + Part: fileSignatureTypes.Extension, + MatchOn: ".tblk", Description: "Tunnelblick VPN configuration file", Comment: "", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "Favorites.plist", + Part: fileSignatureTypes.Filename, + MatchOn: "Favorites.plist", Description: "Sequel Pro MySQL database manager bookmark file", Comment: "", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "configuration.user.xpl", + Part: fileSignatureTypes.Filename, + MatchOn: "configuration.user.xpl", Description: "Little Snitch firewall configuration file", Comment: "Contains traffic rules for applications", }, SimpleSignature{ - Part: PartExtension, - MatchOn: ".dayone", + Part: fileSignatureTypes.Extension, + MatchOn: ".dayone", Description: "Day One journal file", Comment: "Now it's getting creepy...", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "journal.txt", + Part: fileSignatureTypes.Filename, + MatchOn: "journal.txt", Description: "Potential jrnl journal file", Comment: "Now it's getting creepy...", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "knife.rb", + Part: fileSignatureTypes.Filename, + MatchOn: "knife.rb", Description: "Chef Knife configuration file", Comment: "Can contain references to Chef servers", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "proftpdpasswd", + Part: fileSignatureTypes.Filename, + MatchOn: "proftpdpasswd", Description: "cPanel backup ProFTPd credentials file", Comment: "Contains usernames and password hashes for FTP accounts", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "robomongo.json", + Part: fileSignatureTypes.Filename, + MatchOn: "robomongo.json", Description: "Robomongo MongoDB manager configuration file", Comment: "Can contain credentials for MongoDB databases", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "filezilla.xml", + Part: fileSignatureTypes.Filename, + MatchOn: "filezilla.xml", Description: "FileZilla FTP configuration file", Comment: "Can contain credentials for FTP servers", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "recentservers.xml", + Part: fileSignatureTypes.Filename, + MatchOn: "recentservers.xml", Description: "FileZilla FTP recent servers file", Comment: "Can contain credentials for FTP servers", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "ventrilo_srv.ini", + Part: fileSignatureTypes.Filename, + MatchOn: "ventrilo_srv.ini", Description: "Ventrilo server configuration file", Comment: "Can contain passwords", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "terraform.tfvars", + Part: fileSignatureTypes.Filename, + MatchOn: "terraform.tfvars", Description: "Terraform variable config file", Comment: "Can contain credentials for terraform providers", }, SimpleSignature{ - Part: PartFilename, - MatchOn: ".exports", + Part: fileSignatureTypes.Filename, + MatchOn: ".exports", Description: "Shell configuration file", Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", }, SimpleSignature{ - Part: PartFilename, - MatchOn: ".functions", + Part: fileSignatureTypes.Filename, + MatchOn: ".functions", Description: "Shell configuration file", Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", }, SimpleSignature{ - Part: PartFilename, - MatchOn: ".extra", + Part: fileSignatureTypes.Filename, + MatchOn: ".extra", Description: "Shell configuration file", Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^.*_rsa$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^.*_rsa$`), Description: "Private SSH key", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^.*_dsa$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^.*_dsa$`), Description: "Private SSH key", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^.*_ed25519$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^.*_ed25519$`), Description: "Private SSH key", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^.*_ecdsa$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^.*_ecdsa$`), Description: "Private SSH key", Comment: "", }, PatternSignature{ - Part: PartPath, - MatchOn: regexp.MustCompile(`\.?ssh/config$`), + Part: fileSignatureTypes.Path, + MatchOn: regexp.MustCompile(`\.?ssh/config$`), Description: "SSH configuration file", Comment: "", }, PatternSignature{ - Part: PartExtension, - MatchOn: regexp.MustCompile(`^key(pair)?$`), + Part: fileSignatureTypes.Extension, + MatchOn: regexp.MustCompile(`^key(pair)?$`), Description: "Potential cryptographic private key", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_|sh_|z)?history$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_|sh_|z)?history$`), Description: "Shell command history file", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?mysql_history$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?mysql_history$`), Description: "MySQL client command history file", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?psql_history$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?psql_history$`), Description: "PostgreSQL client command history file", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?pgpass$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?pgpass$`), Description: "PostgreSQL password file", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?irb_history$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?irb_history$`), Description: "Ruby IRB console history file", Comment: "", }, PatternSignature{ - Part: PartPath, - MatchOn: regexp.MustCompile(`\.?purple/accounts\.xml$`), + Part: fileSignatureTypes.Path, + MatchOn: regexp.MustCompile(`\.?purple/accounts\.xml$`), Description: "Pidgin chat client account configuration file", Comment: "", }, PatternSignature{ - Part: PartPath, - MatchOn: regexp.MustCompile(`\.?xchat2?/servlist_?\.conf$`), + Part: fileSignatureTypes.Path, + MatchOn: regexp.MustCompile(`\.?xchat2?/servlist_?\.conf$`), Description: "Hexchat/XChat IRC client server list configuration file", Comment: "", }, PatternSignature{ - Part: PartPath, - MatchOn: regexp.MustCompile(`\.?irssi/config$`), + Part: fileSignatureTypes.Path, + MatchOn: regexp.MustCompile(`\.?irssi/config$`), Description: "Irssi IRC client configuration file", Comment: "", }, PatternSignature{ - Part: PartPath, - MatchOn: regexp.MustCompile(`\.?recon-ng/keys\.db$`), + Part: fileSignatureTypes.Path, + MatchOn: regexp.MustCompile(`\.?recon-ng/keys\.db$`), Description: "Recon-ng web reconnaissance framework API key database", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?dbeaver-data-sources.xml$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?dbeaver-data-sources.xml$`), Description: "DBeaver SQL database manager configuration file", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?muttrc$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?muttrc$`), Description: "Mutt e-mail client configuration file", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?s3cfg$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?s3cfg$`), Description: "S3cmd configuration file", Comment: "", }, PatternSignature{ - Part: PartPath, - MatchOn: regexp.MustCompile(`\.?aws/credentials$`), + Part: fileSignatureTypes.Path, + MatchOn: regexp.MustCompile(`\.?aws/credentials$`), Description: "AWS CLI credentials file", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^sftp-config(\.json)?$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^sftp-config(\.json)?$`), Description: "SFTP connection configuration file", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?trc$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?trc$`), Description: "T command-line Twitter client configuration file", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?gitrobrc$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?gitrobrc$`), Description: "Well, this is awkward... Gitrob configuration file", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?(bash|zsh|csh)rc$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?(bash|zsh|csh)rc$`), Description: "Shell configuration file", Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_)?profile$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_)?profile$`), Description: "Shell profile configuration file", Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_)?aliases$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_)?aliases$`), Description: "Shell command alias configuration file", Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`config(\.inc)?\.php$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`config(\.inc)?\.php$`), Description: "PHP configuration file", Comment: "", }, PatternSignature{ - Part: PartExtension, - MatchOn: regexp.MustCompile(`^key(store|ring)$`), + Part: fileSignatureTypes.Extension, + MatchOn: regexp.MustCompile(`^key(store|ring)$`), Description: "GNOME Keyring database file", Comment: "", }, PatternSignature{ - Part: PartExtension, - MatchOn: regexp.MustCompile(`^kdbx?$`), + Part: fileSignatureTypes.Extension, + MatchOn: regexp.MustCompile(`^kdbx?$`), Description: "KeePass password manager database file", Comment: "Feed it to Hashcat and see if you're lucky", }, PatternSignature{ - Part: PartExtension, - MatchOn: regexp.MustCompile(`^sql(dump)?$`), + Part: fileSignatureTypes.Extension, + MatchOn: regexp.MustCompile(`^sql(dump)?$`), Description: "SQL dump file", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?htpasswd$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?htpasswd$`), Description: "Apache htpasswd file", Comment: "", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^(\.|_)?netrc$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^(\.|_)?netrc$`), Description: "Configuration file for auto-login process", Comment: "Can contain username and password", }, PatternSignature{ - Part: PartPath, - MatchOn: regexp.MustCompile(`\.?gem/credentials$`), + Part: fileSignatureTypes.Path, + MatchOn: regexp.MustCompile(`\.?gem/credentials$`), Description: "Rubygems credentials file", Comment: "Can contain API key for a rubygems.org account", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?tugboat$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?tugboat$`), Description: "Tugboat DigitalOcean management tool configuration", Comment: "", }, PatternSignature{ - Part: PartPath, - MatchOn: regexp.MustCompile(`doctl/config.yaml$`), + Part: fileSignatureTypes.Path, + MatchOn: regexp.MustCompile(`doctl/config.yaml$`), Description: "DigitalOcean doctl command-line client configuration file", Comment: "Contains DigitalOcean API key and other information", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?git-credentials$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?git-credentials$`), Description: "git-credential-store helper credentials file", Comment: "", }, PatternSignature{ - Part: PartPath, - MatchOn: regexp.MustCompile(`config/hub$`), + Part: fileSignatureTypes.Path, + MatchOn: regexp.MustCompile(`config/hub$`), Description: "GitHub Hub command-line client configuration file", Comment: "Can contain GitHub API access token", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?gitconfig$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?gitconfig$`), Description: "Git configuration file", Comment: "", }, PatternSignature{ - Part: PartPath, - MatchOn: regexp.MustCompile(`\.?chef/(.*)\.pem$`), + Part: fileSignatureTypes.Path, + MatchOn: regexp.MustCompile(`\.?chef/(.*)\.pem$`), Description: "Chef private key", Comment: "Can be used to authenticate against Chef servers", }, PatternSignature{ - Part: PartPath, - MatchOn: regexp.MustCompile(`etc/shadow$`), + Part: fileSignatureTypes.Path, + MatchOn: regexp.MustCompile(`etc/shadow$`), Description: "Potential Linux shadow file", Comment: "Contains hashed passwords for system users", }, PatternSignature{ - Part: PartPath, - MatchOn: regexp.MustCompile(`etc/passwd$`), + Part: fileSignatureTypes.Path, + MatchOn: regexp.MustCompile(`etc/passwd$`), Description: "Potential Linux passwd file", Comment: "Contains system user information", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?dockercfg$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?dockercfg$`), Description: "Docker configuration file", Comment: "Can contain credentials for public or private Docker registries", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?npmrc$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?npmrc$`), Description: "NPM configuration file", Comment: "Can contain credentials for NPM registries", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^\.?env$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^\.?env$`), Description: "Environment configuration file", Comment: "", }, PatternSignature{ - Part: PartPath, - MatchOn: regexp.MustCompile(`credential`), + Part: fileSignatureTypes.Path, + MatchOn: regexp.MustCompile(`credential`), Description: "Contains word: credential", Comment: "", }, PatternSignature{ - Part: PartPath, - MatchOn: regexp.MustCompile(`password`), + Part: fileSignatureTypes.Path, + MatchOn: regexp.MustCompile(`password`), Description: "Contains word: password", Comment: "", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "credentials.json", + Part: fileSignatureTypes.Filename, + MatchOn: "credentials.json", Description: "Google Cloud Platform service account credentials keyfile", Comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", }, PatternSignature{ - Part: PartFilename, - MatchOn: regexp.MustCompile(`^.*-[a-f0-9]{12}\.json$`), + Part: fileSignatureTypes.Filename, + MatchOn: regexp.MustCompile(`^.*-[a-f0-9]{12}\.json$`), Description: "Google Cloud Platform service account credentials keyfile", Comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "adc.json", + Part: fileSignatureTypes.Filename, + MatchOn: "adc.json", Description: "Legacy Google Cloud Platform service account credentials keyfile", Comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", }, SimpleSignature{ - Part: PartFilename, - MatchOn: "credentials.db", + Part: fileSignatureTypes.Filename, + MatchOn: "credentials.db", Description: "Google Cloud Platform gcloud credential database", Comment: "sqlite database containing credentials used by the gcloud command from Google's Cloud SDK", }, SimpleSignature{ - Part: PartFilename, - MatchOn: ".boto", + Part: fileSignatureTypes.Filename, + MatchOn: ".boto", Description: "Legacy Google Cloud Platform gcloud credential database", Comment: "File containing credentials used by the gcloud command from Google's Cloud SDK", }, -} \ No newline at end of file +} diff --git a/matching/simplesignature.go b/matching/simplesignature.go index 6baab34b..eda3fa9b 100644 --- a/matching/simplesignature.go +++ b/matching/simplesignature.go @@ -10,11 +10,11 @@ type SimpleSignature struct { func (s SimpleSignature) Match(file MatchFile) bool { var haystack *string switch s.Part { - case PartPath: + case fileSignatureTypes.Path: haystack = &file.Path - case PartFilename: + case fileSignatureTypes.Filename: haystack = &file.Filename - case PartExtension: + case fileSignatureTypes.Extension: haystack = &file.Extension default: return false From 498cda37d949b7e7dac9875234c8f0df270e4638 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 17:35:47 -0400 Subject: [PATCH 079/226] name file signature types appropriately --- core/analysis.go | 2 +- main.go | 2 +- matching/interfaces.go | 2 +- matching/patternsignature.go | 8 +- matching/signatures.go | 194 +++++++++++++++++------------------ matching/simplesignature.go | 8 +- 6 files changed, 108 insertions(+), 108 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index 094fb50b..3b73e890 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -168,7 +168,7 @@ func AnalyzeRepositories(sess *Session) { continue } sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.CloneURL, matchFile.Path) - for _, signature := range matching.Signatures { + for _, signature := range matching.FileSignatures { if signature.Match(matchFile) { finding := &matching.Finding{ diff --git a/main.go b/main.go index b1e4fc9a..e87021ec 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,7 @@ func main() { sess.Out.Info("%s\n\n", common.ASCIIBanner) sess.Out.Important("%s v%s started at %s\n", common.Name, common.Version, sess.Stats.StartedAt.Format(time.RFC3339)) - sess.Out.Important("Loaded %d signatures\n", len(matching.Signatures)) + sess.Out.Important("Loaded %d signatures\n", len(matching.FileSignatures)) sess.Out.Important("Web interface available at http://%s:%d\n", *sess.Options.BindAddress, *sess.Options.Port) if sess.Stats.Status == "finished" { diff --git a/matching/interfaces.go b/matching/interfaces.go index a8281a5e..8941fb25 100644 --- a/matching/interfaces.go +++ b/matching/interfaces.go @@ -1,6 +1,6 @@ package matching -type Signature interface { +type FileSignature interface { Match(file MatchFile) bool GetDescription() string GetComment() string diff --git a/matching/patternsignature.go b/matching/patternsignature.go index abc8367b..6e531a2f 100644 --- a/matching/patternsignature.go +++ b/matching/patternsignature.go @@ -2,14 +2,14 @@ package matching import "regexp" -type PatternSignature struct { +type PatternFileSignature struct { Part string MatchOn *regexp.Regexp Description string Comment string } -func (s PatternSignature) Match(file MatchFile) bool { +func (s PatternFileSignature) Match(file MatchFile) bool { var haystack *string switch s.Part { case fileSignatureTypes.Path: @@ -25,10 +25,10 @@ func (s PatternSignature) Match(file MatchFile) bool { return s.MatchOn.MatchString(*haystack) } -func (s PatternSignature) GetDescription() string { +func (s PatternFileSignature) GetDescription() string { return s.Description } -func (s PatternSignature) GetComment() string { +func (s PatternFileSignature) GetComment() string { return s.Comment } diff --git a/matching/signatures.go b/matching/signatures.go index 3765df82..d310c298 100644 --- a/matching/signatures.go +++ b/matching/signatures.go @@ -14,578 +14,578 @@ var fileSignatureTypes = FileSignatureType{ Path: "path", } -var Signatures = []Signature{ - SimpleSignature{ +var FileSignatures = []FileSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".pem", Description: "Potential cryptographic private key", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".log", Description: "Log file", Comment: "Log files can contain secret HTTP endpoints, session IDs, API keys and other goodies", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".pkcs12", Description: "Potential cryptographic key bundle", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".p12", Description: "Potential cryptographic key bundle", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".pfx", Description: "Potential cryptographic key bundle", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".asc", Description: "Potential cryptographic key bundle", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "otr.private_key", Description: "Pidgin OTR private key", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".ovpn", Description: "OpenVPN client configuration file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".cscfg", Description: "Azure service configuration schema file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".rdp", Description: "Remote Desktop connection file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".mdf", Description: "Microsoft SQL database file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".sdf", Description: "Microsoft SQL server compact database file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".sqlite", Description: "SQLite database file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".bek", Description: "Microsoft BitLocker recovery key file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".tpm", Description: "Microsoft BitLocker Trusted Platform Module password file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".fve", Description: "Windows BitLocker full volume encrypted data file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".jks", Description: "Java keystore file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".psafe3", Description: "Password Safe database file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "secret_token.rb", Description: "Ruby On Rails secret token configuration file", Comment: "If the Rails secret token is known, it can allow for remote code execution (http://www.exploit-db.com/exploits/27527/)", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "carrierwave.rb", Description: "Carrierwave configuration file", Comment: "Can contain credentials for cloud storage systems such as Amazon S3 and Google Storage", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "database.yml", Description: "Potential Ruby On Rails database configuration file", Comment: "Can contain database credentials", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "omniauth.rb", Description: "OmniAuth configuration file", Comment: "The OmniAuth configuration file can contain client application secrets", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "settings.py", Description: "Django configuration file", Comment: "Can contain database credentials, cloud storage system credentials, and other secrets", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".agilekeychain", Description: "1Password password manager database file", Comment: "Feed it to Hashcat and see if you're lucky", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".keychain", Description: "Apple Keychain database file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".pcap", Description: "Network traffic capture file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".gnucash", Description: "GnuCash database file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin.xml", Description: "Jenkins publish over SSH plugin file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "credentials.xml", Description: "Potential Jenkins credentials file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".kwallet", Description: "KDE Wallet Manager database file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "LocalSettings.php", Description: "Potential MediaWiki configuration file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".tblk", Description: "Tunnelblick VPN configuration file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "Favorites.plist", Description: "Sequel Pro MySQL database manager bookmark file", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "configuration.user.xpl", Description: "Little Snitch firewall configuration file", Comment: "Contains traffic rules for applications", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".dayone", Description: "Day One journal file", Comment: "Now it's getting creepy...", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "journal.txt", Description: "Potential jrnl journal file", Comment: "Now it's getting creepy...", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "knife.rb", Description: "Chef Knife configuration file", Comment: "Can contain references to Chef servers", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "proftpdpasswd", Description: "cPanel backup ProFTPd credentials file", Comment: "Contains usernames and password hashes for FTP accounts", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "robomongo.json", Description: "Robomongo MongoDB manager configuration file", Comment: "Can contain credentials for MongoDB databases", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "filezilla.xml", Description: "FileZilla FTP configuration file", Comment: "Can contain credentials for FTP servers", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "recentservers.xml", Description: "FileZilla FTP recent servers file", Comment: "Can contain credentials for FTP servers", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "ventrilo_srv.ini", Description: "Ventrilo server configuration file", Comment: "Can contain passwords", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "terraform.tfvars", Description: "Terraform variable config file", Comment: "Can contain credentials for terraform providers", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: ".exports", Description: "Shell configuration file", Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: ".functions", Description: "Shell configuration file", Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: ".extra", Description: "Shell configuration file", Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^.*_rsa$`), Description: "Private SSH key", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^.*_dsa$`), Description: "Private SSH key", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^.*_ed25519$`), Description: "Private SSH key", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^.*_ecdsa$`), Description: "Private SSH key", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Path, MatchOn: regexp.MustCompile(`\.?ssh/config$`), Description: "SSH configuration file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: regexp.MustCompile(`^key(pair)?$`), Description: "Potential cryptographic private key", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_|sh_|z)?history$`), Description: "Shell command history file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?mysql_history$`), Description: "MySQL client command history file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?psql_history$`), Description: "PostgreSQL client command history file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?pgpass$`), Description: "PostgreSQL password file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?irb_history$`), Description: "Ruby IRB console history file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Path, MatchOn: regexp.MustCompile(`\.?purple/accounts\.xml$`), Description: "Pidgin chat client account configuration file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Path, MatchOn: regexp.MustCompile(`\.?xchat2?/servlist_?\.conf$`), Description: "Hexchat/XChat IRC client server list configuration file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Path, MatchOn: regexp.MustCompile(`\.?irssi/config$`), Description: "Irssi IRC client configuration file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Path, MatchOn: regexp.MustCompile(`\.?recon-ng/keys\.db$`), Description: "Recon-ng web reconnaissance framework API key database", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?dbeaver-data-sources.xml$`), Description: "DBeaver SQL database manager configuration file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?muttrc$`), Description: "Mutt e-mail client configuration file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?s3cfg$`), Description: "S3cmd configuration file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Path, MatchOn: regexp.MustCompile(`\.?aws/credentials$`), Description: "AWS CLI credentials file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^sftp-config(\.json)?$`), Description: "SFTP connection configuration file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?trc$`), Description: "T command-line Twitter client configuration file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?gitrobrc$`), Description: "Well, this is awkward... Gitrob configuration file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?(bash|zsh|csh)rc$`), Description: "Shell configuration file", Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_)?profile$`), Description: "Shell profile configuration file", Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_)?aliases$`), Description: "Shell command alias configuration file", Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`config(\.inc)?\.php$`), Description: "PHP configuration file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: regexp.MustCompile(`^key(store|ring)$`), Description: "GNOME Keyring database file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: regexp.MustCompile(`^kdbx?$`), Description: "KeePass password manager database file", Comment: "Feed it to Hashcat and see if you're lucky", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: regexp.MustCompile(`^sql(dump)?$`), Description: "SQL dump file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?htpasswd$`), Description: "Apache htpasswd file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^(\.|_)?netrc$`), Description: "Configuration file for auto-login process", Comment: "Can contain username and password", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Path, MatchOn: regexp.MustCompile(`\.?gem/credentials$`), Description: "Rubygems credentials file", Comment: "Can contain API key for a rubygems.org account", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?tugboat$`), Description: "Tugboat DigitalOcean management tool configuration", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Path, MatchOn: regexp.MustCompile(`doctl/config.yaml$`), Description: "DigitalOcean doctl command-line client configuration file", Comment: "Contains DigitalOcean API key and other information", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?git-credentials$`), Description: "git-credential-store helper credentials file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Path, MatchOn: regexp.MustCompile(`config/hub$`), Description: "GitHub Hub command-line client configuration file", Comment: "Can contain GitHub API access token", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?gitconfig$`), Description: "Git configuration file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Path, MatchOn: regexp.MustCompile(`\.?chef/(.*)\.pem$`), Description: "Chef private key", Comment: "Can be used to authenticate against Chef servers", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Path, MatchOn: regexp.MustCompile(`etc/shadow$`), Description: "Potential Linux shadow file", Comment: "Contains hashed passwords for system users", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Path, MatchOn: regexp.MustCompile(`etc/passwd$`), Description: "Potential Linux passwd file", Comment: "Contains system user information", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?dockercfg$`), Description: "Docker configuration file", Comment: "Can contain credentials for public or private Docker registries", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?npmrc$`), Description: "NPM configuration file", Comment: "Can contain credentials for NPM registries", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^\.?env$`), Description: "Environment configuration file", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Path, MatchOn: regexp.MustCompile(`credential`), Description: "Contains word: credential", Comment: "", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Path, MatchOn: regexp.MustCompile(`password`), Description: "Contains word: password", Comment: "", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "credentials.json", Description: "Google Cloud Platform service account credentials keyfile", Comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", }, - PatternSignature{ + PatternFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: regexp.MustCompile(`^.*-[a-f0-9]{12}\.json$`), Description: "Google Cloud Platform service account credentials keyfile", Comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "adc.json", Description: "Legacy Google Cloud Platform service account credentials keyfile", Comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: "credentials.db", Description: "Google Cloud Platform gcloud credential database", Comment: "sqlite database containing credentials used by the gcloud command from Google's Cloud SDK", }, - SimpleSignature{ + SimpleFileSignature{ Part: fileSignatureTypes.Filename, MatchOn: ".boto", Description: "Legacy Google Cloud Platform gcloud credential database", diff --git a/matching/simplesignature.go b/matching/simplesignature.go index eda3fa9b..fc0a077e 100644 --- a/matching/simplesignature.go +++ b/matching/simplesignature.go @@ -1,13 +1,13 @@ package matching -type SimpleSignature struct { +type SimpleFileSignature struct { Part string MatchOn string Description string Comment string } -func (s SimpleSignature) Match(file MatchFile) bool { +func (s SimpleFileSignature) Match(file MatchFile) bool { var haystack *string switch s.Part { case fileSignatureTypes.Path: @@ -23,10 +23,10 @@ func (s SimpleSignature) Match(file MatchFile) bool { return s.MatchOn == *haystack } -func (s SimpleSignature) GetDescription() string { +func (s SimpleFileSignature) GetDescription() string { return s.Description } -func (s SimpleSignature) GetComment() string { +func (s SimpleFileSignature) GetComment() string { return s.Comment } From 805bc7a8c435c5250cd921693d80375b037e11e8 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 16 Mar 2020 17:39:34 -0400 Subject: [PATCH 080/226] rename files appropriately --- matching/{signatures.go => filesignatures.go} | 0 matching/{filematching.go => matchfile.go} | 0 matching/{patternsignature.go => patternfilesignature.go} | 0 matching/{simplesignature.go => simplefilesignature.go} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename matching/{signatures.go => filesignatures.go} (100%) rename matching/{filematching.go => matchfile.go} (100%) rename matching/{patternsignature.go => patternfilesignature.go} (100%) rename matching/{simplesignature.go => simplefilesignature.go} (100%) diff --git a/matching/signatures.go b/matching/filesignatures.go similarity index 100% rename from matching/signatures.go rename to matching/filesignatures.go diff --git a/matching/filematching.go b/matching/matchfile.go similarity index 100% rename from matching/filematching.go rename to matching/matchfile.go diff --git a/matching/patternsignature.go b/matching/patternfilesignature.go similarity index 100% rename from matching/patternsignature.go rename to matching/patternfilesignature.go diff --git a/matching/simplesignature.go b/matching/simplefilesignature.go similarity index 100% rename from matching/simplesignature.go rename to matching/simplefilesignature.go From 4fd716b2d5639bd4b04c0f2dc01d08598760f5d5 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 19 Mar 2020 12:04:52 -0400 Subject: [PATCH 081/226] organize common functions --- common/io.go | 10 ++++++++++ core/core.go => common/strings.go | 10 +--------- core/analysis.go | 6 +++--- core/session.go | 4 ++-- 4 files changed, 16 insertions(+), 14 deletions(-) create mode 100644 common/io.go rename core/core.go => common/strings.go (75%) diff --git a/common/io.go b/common/io.go new file mode 100644 index 00000000..707612b0 --- /dev/null +++ b/common/io.go @@ -0,0 +1,10 @@ +package common + +import "os" + +func FileExists(path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } + return true +} diff --git a/core/core.go b/common/strings.go similarity index 75% rename from core/core.go rename to common/strings.go index 878a350e..a41048ea 100644 --- a/core/core.go +++ b/common/strings.go @@ -1,21 +1,13 @@ -package core +package common import ( "fmt" - "os" "regexp" "strings" ) var NewlineRegex = regexp.MustCompile(`\r?\n`) -func FileExists(path string) bool { - if _, err := os.Stat(path); os.IsNotExist(err) { - return false - } - return true -} - func Pluralize(count int, singular string, plural string) string { if count == 1 { return singular diff --git a/core/analysis.go b/core/analysis.go index 3b73e890..13473429 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -79,7 +79,7 @@ func GatherRepositories(sess *Session) { sess.AddRepository(repo) } sess.Stats.IncrementTargets() - sess.Out.Info(" Retrieved %d %s from %s\n", len(repos), Pluralize(len(repos), "repository", "repositories"), *target.Login) + sess.Out.Info(" Retrieved %d %s from %s\n", len(repos), common.Pluralize(len(repos), "repository", "repositories"), *target.Login) } }() } @@ -106,7 +106,7 @@ func AnalyzeRepositories(sess *Session) { wg.Add(threadNum) sess.Out.Debug("Threads for repository analysis: %d\n", threadNum) - sess.Out.Important("Analyzing %d %s...\n", len(sess.Repositories), Pluralize(len(sess.Repositories), "repository", "repositories")) + sess.Out.Important("Analyzing %d %s...\n", len(sess.Repositories), common.Pluralize(len(sess.Repositories), "repository", "repositories")) for i := 0; i < threadNum; i++ { go func(tid int) { @@ -188,7 +188,7 @@ func AnalyzeRepositories(sess *Session) { sess.Out.Warn(" %s: %s\n", strings.ToUpper(changeAction), finding.Description) sess.Out.Info(" Path.......: %s\n", finding.FilePath) sess.Out.Info(" Repo.......: %s\n", *repo.CloneURL) - sess.Out.Info(" Message....: %s\n", TruncateString(finding.CommitMessage, 100)) + sess.Out.Info(" Message....: %s\n", common.TruncateString(finding.CommitMessage, 100)) sess.Out.Info(" Author.....: %s\n", finding.CommitAuthor) if finding.Comment != "" { sess.Out.Info(" Comment....: %s\n", finding.Comment) diff --git a/core/session.go b/core/session.go index a875a1c1..4304578c 100644 --- a/core/session.go +++ b/core/session.go @@ -239,12 +239,12 @@ func NewSession() (*Session, error) { return nil, err } - if *session.Options.Save != "" && FileExists(*session.Options.Save) { + if *session.Options.Save != "" && common.FileExists(*session.Options.Save) { return nil, errors.New(fmt.Sprintf("File: %s already exists.", *session.Options.Save)) } if *session.Options.Load != "" { - if !FileExists(*session.Options.Load) { + if !common.FileExists(*session.Options.Load) { return nil, errors.New(fmt.Sprintf("Session file %s does not exist or is not readable.", *session.Options.Load)) } data, err := ioutil.ReadFile(*session.Options.Load) From 3eab47e0deeab7e95a6ce67e28a19bf5bc17379d Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 19 Mar 2020 12:16:48 -0400 Subject: [PATCH 082/226] stub in a type for loading signatures from a flat json file --- file-signatures.json | 9 +++++++++ main.go | 7 ++++++- matching/signatures.go | 20 ++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 file-signatures.json create mode 100644 matching/signatures.go diff --git a/file-signatures.json b/file-signatures.json new file mode 100644 index 00000000..a3a76175 --- /dev/null +++ b/file-signatures.json @@ -0,0 +1,9 @@ +[ + { + "type": "file", + "part": "extension", + "match": ".pem", + "description": "Potential cryptographic private key", + "comment": "" + } +] \ No newline at end of file diff --git a/main.go b/main.go index e87021ec..bb36335f 100644 --- a/main.go +++ b/main.go @@ -21,9 +21,14 @@ func main() { os.Exit(1) } + signatures := matching.Signatures{} + err := signatures.Load(*sess.Options.Mode); if err != nil { + sess.Out.Fatal(err.Error()) + } + sess.Out.Info("%s\n\n", common.ASCIIBanner) sess.Out.Important("%s v%s started at %s\n", common.Name, common.Version, sess.Stats.StartedAt.Format(time.RFC3339)) - sess.Out.Important("Loaded %d signatures\n", len(matching.FileSignatures)) + sess.Out.Important("Loaded %d signatures\n", len(signatures.FileSignatures)) sess.Out.Important("Web interface available at http://%s:%d\n", *sess.Options.BindAddress, *sess.Options.Port) if sess.Stats.Status == "finished" { diff --git a/matching/signatures.go b/matching/signatures.go new file mode 100644 index 00000000..dc7b1225 --- /dev/null +++ b/matching/signatures.go @@ -0,0 +1,20 @@ +package matching + +import ( + "errors" + "fmt" + "github.com/codeEmitter/gitrob/common" +) + +type Signatures struct { + FileSignatures []FileSignature + //ContentSignatures []ContentSignatures +} + +func (s *Signatures) Load(mode int) error { + fileSignaturePath := "./file-signatures.json" + if !common.FileExists("./file-signatures.json") { + return errors.New(fmt.Sprintf("Missing signature file: %s.", fileSignaturePath)) + } + return nil +} From 35dd7aaec25676003bf07dd50cdefdab03cbfb68 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 19 Mar 2020 13:55:19 -0400 Subject: [PATCH 083/226] rename interface for clarity --- matching/filesignatures.go | 2 +- matching/interfaces.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matching/filesignatures.go b/matching/filesignatures.go index d310c298..8b2d0213 100644 --- a/matching/filesignatures.go +++ b/matching/filesignatures.go @@ -14,7 +14,7 @@ var fileSignatureTypes = FileSignatureType{ Path: "path", } -var FileSignatures = []FileSignature{ +var FileSignatures = []IFileSignature{ SimpleFileSignature{ Part: fileSignatureTypes.Extension, MatchOn: ".pem", diff --git a/matching/interfaces.go b/matching/interfaces.go index 8941fb25..d382168f 100644 --- a/matching/interfaces.go +++ b/matching/interfaces.go @@ -1,6 +1,6 @@ package matching -type FileSignature interface { +type IFileSignature interface { Match(file MatchFile) bool GetDescription() string GetComment() string From 6f9bf7ffac4fdc193fe057e73731d06018bf3ee2 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 19 Mar 2020 16:08:46 -0400 Subject: [PATCH 084/226] pull in file signatures from flat json file --- core/analysis.go | 3 +- core/session.go | 7 + file-signatures.json | 9 - filesignatures.json | 676 +++++++++++++++++++++++++++++++++++++ main.go | 8 +- matching/filesignatures.go | 583 +------------------------------- matching/signatures.go | 54 ++- 7 files changed, 737 insertions(+), 603 deletions(-) delete mode 100644 file-signatures.json create mode 100644 filesignatures.json diff --git a/core/analysis.go b/core/analysis.go index 13473429..07ed1a3b 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -168,7 +168,8 @@ func AnalyzeRepositories(sess *Session) { continue } sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.CloneURL, matchFile.Path) - for _, signature := range matching.FileSignatures { + + for _, signature := range sess.Signatures.FileSignatures { if signature.Match(matchFile) { finding := &matching.Finding{ diff --git a/core/session.go b/core/session.go index 4304578c..a307e886 100644 --- a/core/session.go +++ b/core/session.go @@ -62,6 +62,7 @@ type Session struct { Targets []*common.Owner Repositories []*common.Repository Findings []*matching.Finding + Signatures matching.Signatures } func (s *Session) Initialize() { @@ -69,11 +70,17 @@ func (s *Session) Initialize() { s.InitLogger() s.InitThreads() s.InitAccessToken() + s.InitSignatures() s.ValidateTokenConfig() s.InitAPIClient() s.InitRouter() } +func (s *Session) InitSignatures() { + s.Signatures = matching.Signatures{} + s.Signatures.Load(*s.Options.Mode) +} + func (s *Session) Finish() { s.Stats.FinishedAt = time.Now() s.Stats.Status = StatusFinished diff --git a/file-signatures.json b/file-signatures.json deleted file mode 100644 index a3a76175..00000000 --- a/file-signatures.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "type": "file", - "part": "extension", - "match": ".pem", - "description": "Potential cryptographic private key", - "comment": "" - } -] \ No newline at end of file diff --git a/filesignatures.json b/filesignatures.json new file mode 100644 index 00000000..dbaead82 --- /dev/null +++ b/filesignatures.json @@ -0,0 +1,676 @@ +{ + "FileSignatures": [ + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^.*_rsa$", + "Description": "Private SSH key", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^.*_dsa$", + "Description": "Private SSH key", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^.*_ed25519$", + "Description": "Private SSH key", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^.*_ecdsa$", + "Description": "Private SSH key", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "path", + "MatchOn": "\\.?ssh/config$", + "Description": "SSH configuration file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "extension", + "MatchOn": "^key(pair)?$", + "Description": "Potential cryptographic private key", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?(bash_|zsh_|sh_|z)?history$", + "Description": "Shell command history file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?mysql_history$", + "Description": "MySQL client command history file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?psql_history$", + "Description": "PostgreSQL client command history file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?pgpass$", + "Description": "PostgreSQL password file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?irb_history$", + "Description": "Ruby IRB console history file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "path", + "MatchOn": "\\.?purple/accounts\\.xml$", + "Description": "Pidgin chat client account configuration file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "path", + "MatchOn": "\\.?xchat2?/servlist_?\\.conf$", + "Description": "Hexchat/XChat IRC client server list configuration file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "path", + "MatchOn": "\\.?irssi/config$", + "Description": "Irssi IRC client configuration file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "path", + "MatchOn": "\\.?recon-ng/keys\\.db$", + "Description": "Recon-ng web reconnaissance framework API key database", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?dbeaver-data-sources.xml$", + "Description": "DBeaver SQL database manager configuration file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?muttrc$", + "Description": "Mutt e-mail client configuration file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?s3cfg$", + "Description": "S3cmd configuration file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "path", + "MatchOn": "\\.?aws/credentials$", + "Description": "AWS CLI credentials file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^sftp-config(\\.json)?$", + "Description": "SFTP connection configuration file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?trc$", + "Description": "T command-line Twitter client configuration file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?gitrobrc$", + "Description": "Well, this is awkward... Gitrob configuration file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?(bash|zsh|csh)rc$", + "Description": "Shell configuration file", + "Comment": "Shell configuration files can contain passwords, API keys, hostnames and other goodies" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?(bash_|zsh_)?profile$", + "Description": "Shell profile configuration file", + "Comment": "Shell configuration files can contain passwords, API keys, hostnames and other goodies" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "`^\\.?(bash_|zsh_)?aliases$", + "Description": "Shell command alias configuration file", + "Comment": "Shell configuration files can contain passwords, API keys, hostnames and other goodies" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "config(\\.inc)?\\.php$", + "Description": "PHP configuration file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "extension", + "MatchOn": "^key(store|ring)$", + "Description": "GNOME Keyring database file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "extension", + "MatchOn": "^kdbx?$", + "Description": "KeePass password manager database file", + "Comment": "Feed it to Hashcat and see if you're lucky" + }, + { + "Kind": "regex", + "Part": "extension", + "MatchOn": "^sql(dump)?$", + "Description": "SQL dump file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?htpasswd$", + "Description": "Apache htpasswd file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^(\\.|_)?netrc$", + "Description": "Configuration file for auto-login process", + "Comment": "Can contain username and password" + }, + { + "Kind": "regex", + "Part": "path", + "MatchOn": "", + "Description": "Rubygems credentials file", + "Comment": "Can contain API key for a rubygems.org account" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?tugboat$", + "Description": "Tugboat DigitalOcean management tool configuration", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "path", + "MatchOn": "doctl/config.yaml$", + "Description": "DigitalOcean doctl command-line client configuration file", + "Comment": "Contains DigitalOcean API key and other information" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?git-credentials$", + "Description": "git-credential-store helper credentials file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "path", + "MatchOn": "config/hub$", + "Description": "GitHub Hub command-line client configuration file", + "Comment": "Can contain GitHub API access token" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?gitconfig$", + "Description": "Git configuration file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "path", + "MatchOn": "\\.?chef/(.*)\\.pem$", + "Description": "Chef private key", + "Comment": "Can be used to authenticate against Chef servers" + }, + { + "Kind": "regex", + "Part": "path", + "MatchOn": "etc/shadow$", + "Description": "Potential Linux shadow file", + "Comment": "Contains hashed passwords for system users" + }, + { + "Kind": "regex", + "Part": "path", + "MatchOn": "etc/passwd$", + "Description": "Potential Linux passwd file", + "Comment": "Contains system user information" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?dockercfg$", + "Description": "Docker configuration file", + "Comment": "Can contain credentials for public or private Docker registries" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?npmrc$", + "Description": "NPM configuration file", + "Comment": "Can contain credentials for NPM registries" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^\\.?env$", + "Description": "Environment configuration file", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "path", + "MatchOn": "credential", + "Description": "Contains word: credential", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "path", + "MatchOn": "password", + "Description": "Contains word: password", + "Comment": "" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "credentials.json", + "Description": "Google Cloud Platform service account credentials keyfile", + "Comment": "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)" + }, + { + "Kind": "regex", + "Part": "filename", + "MatchOn": "^.*-[a-f0-9]{12}\\.json$", + "Description": "Google Cloud Platform service account credentials keyfile", + "Comment": "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "adc.json", + "Description": "Legacy Google Cloud Platform service account credentials keyfile", + "Comment": "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "credentials.db", + "Description": "Google Cloud Platform gcloud credential database", + "Comment": "sqlite database containing credentials used by the gcloud command from Google's Cloud SDK" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": ".boto", + "Description": "Legacy Google Cloud Platform gcloud credential database", + "Comment": "File containing credentials used by the gcloud command from Google's Cloud SDK" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".pem", + "Description": "Potential cryptographic private key", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".log", + "Description": "Log file", + "Comment": "Log files can contain secret HTTP endpoints, session IDs, API keys and other goodies" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".pkcs12", + "Description": "Potential cryptographic key bundle", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".p12", + "Description": "Potential cryptographic key bundle", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".pfx", + "Description": "Potential cryptographic key bundle", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".asc", + "Description": "Potential cryptographic key bundle", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "otr.private_key", + "Description": "Pidgin OTR private key", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".ovpn", + "Description": "OpenVPN client configuration file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".cscfg", + "Description": "Azure service configuration schema file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".rdp", + "Description": "Remote Desktop connection file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".mdf", + "Description": "Microsoft SQL database file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".sdf", + "Description": "Microsoft SQL server compact database file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".sqlite", + "Description": "SQLite database file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".bek", + "Description": "Microsoft BitLocker recovery key file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".tpm", + "Description": "Microsoft BitLocker Trusted Platform Module password file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".fve", + "Description": "Windows BitLocker full volume encrypted data file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".jks", + "Description": "Java keystore file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".psafe3", + "Description": "Password Safe database file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "secret_token.rb", + "Description": "Ruby On Rails secret token configuration file", + "Comment": "If the Rails secret token is known, it can allow for remote code execution (http://www.exploit-db.com/exploits/27527/)" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "carrierwave.rb", + "Description": "Carrierwave configuration file", + "Comment": "Can contain credentials for cloud storage systems such as Amazon S3 and Google Storage" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "database.yml", + "Description": "Potential Ruby On Rails database configuration file", + "Comment": "Can contain database credentials" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "omniauth.rb", + "Description": "OmniAuth configuration file", + "Comment": "The OmniAuth configuration file can contain client application secrets" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "settings.py", + "Description": "Django configuration file", + "Comment": "Can contain database credentials, cloud storage system credentials, and other secrets" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".agilekeychain", + "Description": "1Password password manager database file", + "Comment": "Feed it to Hashcat and see if you're lucky" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".keychain", + "Description": "Apple Keychain database file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".pcap", + "Description": "Network traffic capture file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".gnucash", + "Description": "GnuCash database file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin.xml", + "Description": "Jenkins publish over SSH plugin file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "credentials.xml", + "Description": "Potential Jenkins credentials file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".kwallet", + "Description": "KDE Wallet Manager database file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "LocalSettings.php", + "Description": "Potential MediaWiki configuration file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".tblk", + "Description": "Tunnelblick VPN configuration file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "Favorites.plist", + "Description": "Sequel Pro MySQL database manager bookmark file", + "Comment": "" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "configuration.user.xpl", + "Description": "Little Snitch firewall configuration file", + "Comment": "Contains traffic rules for applications" + }, + { + "Kind": "simple", + "Part": "extension", + "MatchOn": ".dayone", + "Description": "Day One journal file", + "Comment": "Now it's getting creepy..." + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "journal.txt", + "Description": "Potential jrnl journal file", + "Comment": "Now it's getting creepy..." + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "knife.rb", + "Description": "Chef Knife configuration file", + "Comment": "Can contain references to Chef servers" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "proftpdpasswd", + "Description": "cPanel backup ProFTPd credentials file", + "Comment": "Contains usernames and password hashes for FTP accounts" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "robomongo.json", + "Description": "Robomongo MongoDB manager configuration file", + "Comment": "Can contain credentials for MongoDB databases" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "filezilla.xml", + "Description": "FileZilla FTP configuration file", + "Comment": "Can contain credentials for FTP servers" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "recentservers.xml", + "Description": "FileZilla FTP recent servers file", + "Comment": "Can contain credentials for FTP servers" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "ventrilo_srv.ini", + "Description": "Ventrilo server configuration file", + "Comment": "Can contain passwords" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": "terraform.tfvars", + "Description": "Terraform variable config file", + "Comment": "Can contain credentials for terraform providers" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": ".exports", + "Description": "Shell configuration file", + "Comment": "Shell configuration files can contain passwords, API keys, hostnames and other goodies" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": ".functions", + "Description": "Shell configuration file", + "Comment": "Shell configuration files can contain passwords, API keys, hostnames and other goodies" + }, + { + "Kind": "simple", + "Part": "filename", + "MatchOn": ".extra", + "Description": "Shell configuration file", + "Comment": "Shell configuration files can contain passwords, API keys, hostnames and other goodies" + } + ] +} \ No newline at end of file diff --git a/main.go b/main.go index bb36335f..84d6647e 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "github.com/codeEmitter/gitrob/matching" "os" "time" @@ -21,14 +20,9 @@ func main() { os.Exit(1) } - signatures := matching.Signatures{} - err := signatures.Load(*sess.Options.Mode); if err != nil { - sess.Out.Fatal(err.Error()) - } - sess.Out.Info("%s\n\n", common.ASCIIBanner) sess.Out.Important("%s v%s started at %s\n", common.Name, common.Version, sess.Stats.StartedAt.Format(time.RFC3339)) - sess.Out.Important("Loaded %d signatures\n", len(signatures.FileSignatures)) + sess.Out.Important("Loaded %d signatures\n", len(sess.Signatures.FileSignatures) + len(sess.Signatures.ContentSignatures)) sess.Out.Important("Web interface available at http://%s:%d\n", *sess.Options.BindAddress, *sess.Options.Port) if sess.Stats.Status == "finished" { diff --git a/matching/filesignatures.go b/matching/filesignatures.go index 8b2d0213..c0efd84b 100644 --- a/matching/filesignatures.go +++ b/matching/filesignatures.go @@ -1,7 +1,5 @@ package matching -import "regexp" - type FileSignatureType struct { Extension string Filename string @@ -12,583 +10,4 @@ var fileSignatureTypes = FileSignatureType{ Extension: "extension", Filename: "filename", Path: "path", -} - -var FileSignatures = []IFileSignature{ - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".pem", - Description: "Potential cryptographic private key", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".log", - Description: "Log file", - Comment: "Log files can contain secret HTTP endpoints, session IDs, API keys and other goodies", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".pkcs12", - Description: "Potential cryptographic key bundle", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".p12", - Description: "Potential cryptographic key bundle", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".pfx", - Description: "Potential cryptographic key bundle", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".asc", - Description: "Potential cryptographic key bundle", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "otr.private_key", - Description: "Pidgin OTR private key", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".ovpn", - Description: "OpenVPN client configuration file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".cscfg", - Description: "Azure service configuration schema file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".rdp", - Description: "Remote Desktop connection file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".mdf", - Description: "Microsoft SQL database file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".sdf", - Description: "Microsoft SQL server compact database file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".sqlite", - Description: "SQLite database file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".bek", - Description: "Microsoft BitLocker recovery key file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".tpm", - Description: "Microsoft BitLocker Trusted Platform Module password file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".fve", - Description: "Windows BitLocker full volume encrypted data file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".jks", - Description: "Java keystore file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".psafe3", - Description: "Password Safe database file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "secret_token.rb", - Description: "Ruby On Rails secret token configuration file", - Comment: "If the Rails secret token is known, it can allow for remote code execution (http://www.exploit-db.com/exploits/27527/)", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "carrierwave.rb", - Description: "Carrierwave configuration file", - Comment: "Can contain credentials for cloud storage systems such as Amazon S3 and Google Storage", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "database.yml", - Description: "Potential Ruby On Rails database configuration file", - Comment: "Can contain database credentials", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "omniauth.rb", - Description: "OmniAuth configuration file", - Comment: "The OmniAuth configuration file can contain client application secrets", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "settings.py", - Description: "Django configuration file", - Comment: "Can contain database credentials, cloud storage system credentials, and other secrets", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".agilekeychain", - Description: "1Password password manager database file", - Comment: "Feed it to Hashcat and see if you're lucky", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".keychain", - Description: "Apple Keychain database file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".pcap", - Description: "Network traffic capture file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".gnucash", - Description: "GnuCash database file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin.xml", - Description: "Jenkins publish over SSH plugin file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "credentials.xml", - Description: "Potential Jenkins credentials file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".kwallet", - Description: "KDE Wallet Manager database file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "LocalSettings.php", - Description: "Potential MediaWiki configuration file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".tblk", - Description: "Tunnelblick VPN configuration file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "Favorites.plist", - Description: "Sequel Pro MySQL database manager bookmark file", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "configuration.user.xpl", - Description: "Little Snitch firewall configuration file", - Comment: "Contains traffic rules for applications", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: ".dayone", - Description: "Day One journal file", - Comment: "Now it's getting creepy...", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "journal.txt", - Description: "Potential jrnl journal file", - Comment: "Now it's getting creepy...", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "knife.rb", - Description: "Chef Knife configuration file", - Comment: "Can contain references to Chef servers", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "proftpdpasswd", - Description: "cPanel backup ProFTPd credentials file", - Comment: "Contains usernames and password hashes for FTP accounts", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "robomongo.json", - Description: "Robomongo MongoDB manager configuration file", - Comment: "Can contain credentials for MongoDB databases", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "filezilla.xml", - Description: "FileZilla FTP configuration file", - Comment: "Can contain credentials for FTP servers", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "recentservers.xml", - Description: "FileZilla FTP recent servers file", - Comment: "Can contain credentials for FTP servers", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "ventrilo_srv.ini", - Description: "Ventrilo server configuration file", - Comment: "Can contain passwords", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "terraform.tfvars", - Description: "Terraform variable config file", - Comment: "Can contain credentials for terraform providers", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: ".exports", - Description: "Shell configuration file", - Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: ".functions", - Description: "Shell configuration file", - Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: ".extra", - Description: "Shell configuration file", - Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^.*_rsa$`), - Description: "Private SSH key", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^.*_dsa$`), - Description: "Private SSH key", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^.*_ed25519$`), - Description: "Private SSH key", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^.*_ecdsa$`), - Description: "Private SSH key", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Path, - MatchOn: regexp.MustCompile(`\.?ssh/config$`), - Description: "SSH configuration file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: regexp.MustCompile(`^key(pair)?$`), - Description: "Potential cryptographic private key", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_|sh_|z)?history$`), - Description: "Shell command history file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?mysql_history$`), - Description: "MySQL client command history file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?psql_history$`), - Description: "PostgreSQL client command history file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?pgpass$`), - Description: "PostgreSQL password file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?irb_history$`), - Description: "Ruby IRB console history file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Path, - MatchOn: regexp.MustCompile(`\.?purple/accounts\.xml$`), - Description: "Pidgin chat client account configuration file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Path, - MatchOn: regexp.MustCompile(`\.?xchat2?/servlist_?\.conf$`), - Description: "Hexchat/XChat IRC client server list configuration file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Path, - MatchOn: regexp.MustCompile(`\.?irssi/config$`), - Description: "Irssi IRC client configuration file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Path, - MatchOn: regexp.MustCompile(`\.?recon-ng/keys\.db$`), - Description: "Recon-ng web reconnaissance framework API key database", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?dbeaver-data-sources.xml$`), - Description: "DBeaver SQL database manager configuration file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?muttrc$`), - Description: "Mutt e-mail client configuration file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?s3cfg$`), - Description: "S3cmd configuration file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Path, - MatchOn: regexp.MustCompile(`\.?aws/credentials$`), - Description: "AWS CLI credentials file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^sftp-config(\.json)?$`), - Description: "SFTP connection configuration file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?trc$`), - Description: "T command-line Twitter client configuration file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?gitrobrc$`), - Description: "Well, this is awkward... Gitrob configuration file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?(bash|zsh|csh)rc$`), - Description: "Shell configuration file", - Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_)?profile$`), - Description: "Shell profile configuration file", - Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?(bash_|zsh_)?aliases$`), - Description: "Shell command alias configuration file", - Comment: "Shell configuration files can contain passwords, API keys, hostnames and other goodies", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`config(\.inc)?\.php$`), - Description: "PHP configuration file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: regexp.MustCompile(`^key(store|ring)$`), - Description: "GNOME Keyring database file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: regexp.MustCompile(`^kdbx?$`), - Description: "KeePass password manager database file", - Comment: "Feed it to Hashcat and see if you're lucky", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Extension, - MatchOn: regexp.MustCompile(`^sql(dump)?$`), - Description: "SQL dump file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?htpasswd$`), - Description: "Apache htpasswd file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^(\.|_)?netrc$`), - Description: "Configuration file for auto-login process", - Comment: "Can contain username and password", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Path, - MatchOn: regexp.MustCompile(`\.?gem/credentials$`), - Description: "Rubygems credentials file", - Comment: "Can contain API key for a rubygems.org account", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?tugboat$`), - Description: "Tugboat DigitalOcean management tool configuration", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Path, - MatchOn: regexp.MustCompile(`doctl/config.yaml$`), - Description: "DigitalOcean doctl command-line client configuration file", - Comment: "Contains DigitalOcean API key and other information", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?git-credentials$`), - Description: "git-credential-store helper credentials file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Path, - MatchOn: regexp.MustCompile(`config/hub$`), - Description: "GitHub Hub command-line client configuration file", - Comment: "Can contain GitHub API access token", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?gitconfig$`), - Description: "Git configuration file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Path, - MatchOn: regexp.MustCompile(`\.?chef/(.*)\.pem$`), - Description: "Chef private key", - Comment: "Can be used to authenticate against Chef servers", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Path, - MatchOn: regexp.MustCompile(`etc/shadow$`), - Description: "Potential Linux shadow file", - Comment: "Contains hashed passwords for system users", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Path, - MatchOn: regexp.MustCompile(`etc/passwd$`), - Description: "Potential Linux passwd file", - Comment: "Contains system user information", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?dockercfg$`), - Description: "Docker configuration file", - Comment: "Can contain credentials for public or private Docker registries", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?npmrc$`), - Description: "NPM configuration file", - Comment: "Can contain credentials for NPM registries", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^\.?env$`), - Description: "Environment configuration file", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Path, - MatchOn: regexp.MustCompile(`credential`), - Description: "Contains word: credential", - Comment: "", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Path, - MatchOn: regexp.MustCompile(`password`), - Description: "Contains word: password", - Comment: "", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "credentials.json", - Description: "Google Cloud Platform service account credentials keyfile", - Comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", - }, - PatternFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: regexp.MustCompile(`^.*-[a-f0-9]{12}\.json$`), - Description: "Google Cloud Platform service account credentials keyfile", - Comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "adc.json", - Description: "Legacy Google Cloud Platform service account credentials keyfile", - Comment: "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: "credentials.db", - Description: "Google Cloud Platform gcloud credential database", - Comment: "sqlite database containing credentials used by the gcloud command from Google's Cloud SDK", - }, - SimpleFileSignature{ - Part: fileSignatureTypes.Filename, - MatchOn: ".boto", - Description: "Legacy Google Cloud Platform gcloud credential database", - Comment: "File containing credentials used by the gcloud command from Google's Cloud SDK", - }, -} +} \ No newline at end of file diff --git a/matching/signatures.go b/matching/signatures.go index dc7b1225..b31d12ff 100644 --- a/matching/signatures.go +++ b/matching/signatures.go @@ -1,20 +1,66 @@ package matching import ( + "encoding/json" "errors" "fmt" "github.com/codeEmitter/gitrob/common" + "io/ioutil" ) type Signatures struct { FileSignatures []FileSignature - //ContentSignatures []ContentSignatures + ContentSignatures []ContentSignature +} + +type ContentSignature struct { + MatchOn string + Description string + Comment string +} + +type FileSignature struct { + Kind string + Part string + MatchOn string + Description string + Comment string } func (s *Signatures) Load(mode int) error { - fileSignaturePath := "./file-signatures.json" - if !common.FileExists("./file-signatures.json") { - return errors.New(fmt.Sprintf("Missing signature file: %s.", fileSignaturePath)) + fileSignaturePath := "./filesignatures.json" + if !common.FileExists(fileSignaturePath) { + return errors.New(fmt.Sprintf("Missing signature file: %s.\n", fileSignaturePath)) + } + data, err := ioutil.ReadFile(fileSignaturePath); if err != nil { + return err + } + if err2 := json.Unmarshal(data, &s); err2 != nil { + return err2 } return nil } + +func (s FileSignature) Match(file MatchFile) bool { + var haystack *string + switch s.Part { + case fileSignatureTypes.Path: + haystack = &file.Path + case fileSignatureTypes.Filename: + haystack = &file.Filename + case fileSignatureTypes.Extension: + haystack = &file.Extension + default: + return false + } + + return s.MatchOn == *haystack +} + +func (s FileSignature) GetDescription() string { + return s.Description +} + +func (s FileSignature) GetComment() string { + return s.Comment +} From 2a0c84655b6621e7e283320a34268eec2e3e1b12 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 19 Mar 2020 16:09:11 -0400 Subject: [PATCH 085/226] update mode option description --- core/options.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/options.go b/core/options.go index 882cfffc..35856a31 100644 --- a/core/options.go +++ b/core/options.go @@ -25,7 +25,7 @@ func ParseOptions() (Options, error) { CommitDepth: flag.Int("commit-depth", 500, "Number of repository commits to process"), GithubAccessToken: flag.String("github-access-token", "", "GitHub access token to use for API requests"), GitLabAccessToken: flag.String("gitlab-access-token", "", "GitLab access token to use for API requests"), - Mode: flag.Int("mode", 1, "Matching mode (see documentation)."), + Mode: flag.Int("mode", 1, "Secrets matching mode (see documentation)."), NoExpandOrgs: flag.Bool("no-expand-orgs", false, "Don't add members to targets when processing organizations"), Threads: flag.Int("threads", 0, "Number of concurrent threads (default number of logical CPUs)"), Save: flag.String("save", "", "Save session to file"), @@ -40,4 +40,4 @@ func ParseOptions() (Options, error) { options.Logins = flag.Args() return options, nil -} +} \ No newline at end of file From 95ce0d44c44564ef752f19a14e2d559454ad56f2 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 19 Mar 2020 17:13:23 -0400 Subject: [PATCH 086/226] WIP: perform matches for compiled regexes --- core/session.go | 5 ++ filesignatures.json | 96 -------------------------------------- matching/filesignatures.go | 34 ++++++++++++++ matching/signatures.go | 31 ------------ 4 files changed, 39 insertions(+), 127 deletions(-) diff --git a/core/session.go b/core/session.go index a307e886..6686fa66 100644 --- a/core/session.go +++ b/core/session.go @@ -7,6 +7,7 @@ import ( "github.com/codeEmitter/gitrob/matching" "io/ioutil" "os" + "regexp" "runtime" "sync" "time" @@ -79,6 +80,10 @@ func (s *Session) Initialize() { func (s *Session) InitSignatures() { s.Signatures = matching.Signatures{} s.Signatures.Load(*s.Options.Mode) + for _, fileSignature := range s.Signatures.FileSignatures { + s.Out.Info(fileSignature.MatchOn) + fileSignature.CompiledRegex = regexp.MustCompile(fileSignature.MatchOn) + } } func (s *Session) Finish() { diff --git a/filesignatures.json b/filesignatures.json index dbaead82..cdee3e5f 100644 --- a/filesignatures.json +++ b/filesignatures.json @@ -1,672 +1,576 @@ { "FileSignatures": [ { - "Kind": "regex", "Part": "filename", "MatchOn": "^.*_rsa$", "Description": "Private SSH key", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^.*_dsa$", "Description": "Private SSH key", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^.*_ed25519$", "Description": "Private SSH key", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^.*_ecdsa$", "Description": "Private SSH key", "Comment": "" }, { - "Kind": "regex", "Part": "path", "MatchOn": "\\.?ssh/config$", "Description": "SSH configuration file", "Comment": "" }, { - "Kind": "regex", "Part": "extension", "MatchOn": "^key(pair)?$", "Description": "Potential cryptographic private key", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?(bash_|zsh_|sh_|z)?history$", "Description": "Shell command history file", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?mysql_history$", "Description": "MySQL client command history file", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?psql_history$", "Description": "PostgreSQL client command history file", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?pgpass$", "Description": "PostgreSQL password file", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?irb_history$", "Description": "Ruby IRB console history file", "Comment": "" }, { - "Kind": "regex", "Part": "path", "MatchOn": "\\.?purple/accounts\\.xml$", "Description": "Pidgin chat client account configuration file", "Comment": "" }, { - "Kind": "regex", "Part": "path", "MatchOn": "\\.?xchat2?/servlist_?\\.conf$", "Description": "Hexchat/XChat IRC client server list configuration file", "Comment": "" }, { - "Kind": "regex", "Part": "path", "MatchOn": "\\.?irssi/config$", "Description": "Irssi IRC client configuration file", "Comment": "" }, { - "Kind": "regex", "Part": "path", "MatchOn": "\\.?recon-ng/keys\\.db$", "Description": "Recon-ng web reconnaissance framework API key database", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?dbeaver-data-sources.xml$", "Description": "DBeaver SQL database manager configuration file", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?muttrc$", "Description": "Mutt e-mail client configuration file", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?s3cfg$", "Description": "S3cmd configuration file", "Comment": "" }, { - "Kind": "regex", "Part": "path", "MatchOn": "\\.?aws/credentials$", "Description": "AWS CLI credentials file", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^sftp-config(\\.json)?$", "Description": "SFTP connection configuration file", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?trc$", "Description": "T command-line Twitter client configuration file", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?gitrobrc$", "Description": "Well, this is awkward... Gitrob configuration file", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?(bash|zsh|csh)rc$", "Description": "Shell configuration file", "Comment": "Shell configuration files can contain passwords, API keys, hostnames and other goodies" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?(bash_|zsh_)?profile$", "Description": "Shell profile configuration file", "Comment": "Shell configuration files can contain passwords, API keys, hostnames and other goodies" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "`^\\.?(bash_|zsh_)?aliases$", "Description": "Shell command alias configuration file", "Comment": "Shell configuration files can contain passwords, API keys, hostnames and other goodies" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "config(\\.inc)?\\.php$", "Description": "PHP configuration file", "Comment": "" }, { - "Kind": "regex", "Part": "extension", "MatchOn": "^key(store|ring)$", "Description": "GNOME Keyring database file", "Comment": "" }, { - "Kind": "regex", "Part": "extension", "MatchOn": "^kdbx?$", "Description": "KeePass password manager database file", "Comment": "Feed it to Hashcat and see if you're lucky" }, { - "Kind": "regex", "Part": "extension", "MatchOn": "^sql(dump)?$", "Description": "SQL dump file", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?htpasswd$", "Description": "Apache htpasswd file", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^(\\.|_)?netrc$", "Description": "Configuration file for auto-login process", "Comment": "Can contain username and password" }, { - "Kind": "regex", "Part": "path", "MatchOn": "", "Description": "Rubygems credentials file", "Comment": "Can contain API key for a rubygems.org account" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?tugboat$", "Description": "Tugboat DigitalOcean management tool configuration", "Comment": "" }, { - "Kind": "regex", "Part": "path", "MatchOn": "doctl/config.yaml$", "Description": "DigitalOcean doctl command-line client configuration file", "Comment": "Contains DigitalOcean API key and other information" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?git-credentials$", "Description": "git-credential-store helper credentials file", "Comment": "" }, { - "Kind": "regex", "Part": "path", "MatchOn": "config/hub$", "Description": "GitHub Hub command-line client configuration file", "Comment": "Can contain GitHub API access token" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?gitconfig$", "Description": "Git configuration file", "Comment": "" }, { - "Kind": "regex", "Part": "path", "MatchOn": "\\.?chef/(.*)\\.pem$", "Description": "Chef private key", "Comment": "Can be used to authenticate against Chef servers" }, { - "Kind": "regex", "Part": "path", "MatchOn": "etc/shadow$", "Description": "Potential Linux shadow file", "Comment": "Contains hashed passwords for system users" }, { - "Kind": "regex", "Part": "path", "MatchOn": "etc/passwd$", "Description": "Potential Linux passwd file", "Comment": "Contains system user information" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?dockercfg$", "Description": "Docker configuration file", "Comment": "Can contain credentials for public or private Docker registries" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?npmrc$", "Description": "NPM configuration file", "Comment": "Can contain credentials for NPM registries" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^\\.?env$", "Description": "Environment configuration file", "Comment": "" }, { - "Kind": "regex", "Part": "path", "MatchOn": "credential", "Description": "Contains word: credential", "Comment": "" }, { - "Kind": "regex", "Part": "path", "MatchOn": "password", "Description": "Contains word: password", "Comment": "" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "credentials.json", "Description": "Google Cloud Platform service account credentials keyfile", "Comment": "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)" }, { - "Kind": "regex", "Part": "filename", "MatchOn": "^.*-[a-f0-9]{12}\\.json$", "Description": "Google Cloud Platform service account credentials keyfile", "Comment": "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "adc.json", "Description": "Legacy Google Cloud Platform service account credentials keyfile", "Comment": "GCP service account credentials can be activated using the gcloud command from Google's Cloud SDK (https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account)" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "credentials.db", "Description": "Google Cloud Platform gcloud credential database", "Comment": "sqlite database containing credentials used by the gcloud command from Google's Cloud SDK" }, { - "Kind": "simple", "Part": "filename", "MatchOn": ".boto", "Description": "Legacy Google Cloud Platform gcloud credential database", "Comment": "File containing credentials used by the gcloud command from Google's Cloud SDK" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".pem", "Description": "Potential cryptographic private key", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".log", "Description": "Log file", "Comment": "Log files can contain secret HTTP endpoints, session IDs, API keys and other goodies" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".pkcs12", "Description": "Potential cryptographic key bundle", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".p12", "Description": "Potential cryptographic key bundle", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".pfx", "Description": "Potential cryptographic key bundle", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".asc", "Description": "Potential cryptographic key bundle", "Comment": "" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "otr.private_key", "Description": "Pidgin OTR private key", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".ovpn", "Description": "OpenVPN client configuration file", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".cscfg", "Description": "Azure service configuration schema file", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".rdp", "Description": "Remote Desktop connection file", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".mdf", "Description": "Microsoft SQL database file", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".sdf", "Description": "Microsoft SQL server compact database file", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".sqlite", "Description": "SQLite database file", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".bek", "Description": "Microsoft BitLocker recovery key file", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".tpm", "Description": "Microsoft BitLocker Trusted Platform Module password file", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".fve", "Description": "Windows BitLocker full volume encrypted data file", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".jks", "Description": "Java keystore file", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".psafe3", "Description": "Password Safe database file", "Comment": "" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "secret_token.rb", "Description": "Ruby On Rails secret token configuration file", "Comment": "If the Rails secret token is known, it can allow for remote code execution (http://www.exploit-db.com/exploits/27527/)" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "carrierwave.rb", "Description": "Carrierwave configuration file", "Comment": "Can contain credentials for cloud storage systems such as Amazon S3 and Google Storage" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "database.yml", "Description": "Potential Ruby On Rails database configuration file", "Comment": "Can contain database credentials" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "omniauth.rb", "Description": "OmniAuth configuration file", "Comment": "The OmniAuth configuration file can contain client application secrets" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "settings.py", "Description": "Django configuration file", "Comment": "Can contain database credentials, cloud storage system credentials, and other secrets" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".agilekeychain", "Description": "1Password password manager database file", "Comment": "Feed it to Hashcat and see if you're lucky" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".keychain", "Description": "Apple Keychain database file", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".pcap", "Description": "Network traffic capture file", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".gnucash", "Description": "GnuCash database file", "Comment": "" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin.xml", "Description": "Jenkins publish over SSH plugin file", "Comment": "" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "credentials.xml", "Description": "Potential Jenkins credentials file", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".kwallet", "Description": "KDE Wallet Manager database file", "Comment": "" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "LocalSettings.php", "Description": "Potential MediaWiki configuration file", "Comment": "" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".tblk", "Description": "Tunnelblick VPN configuration file", "Comment": "" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "Favorites.plist", "Description": "Sequel Pro MySQL database manager bookmark file", "Comment": "" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "configuration.user.xpl", "Description": "Little Snitch firewall configuration file", "Comment": "Contains traffic rules for applications" }, { - "Kind": "simple", "Part": "extension", "MatchOn": ".dayone", "Description": "Day One journal file", "Comment": "Now it's getting creepy..." }, { - "Kind": "simple", "Part": "filename", "MatchOn": "journal.txt", "Description": "Potential jrnl journal file", "Comment": "Now it's getting creepy..." }, { - "Kind": "simple", "Part": "filename", "MatchOn": "knife.rb", "Description": "Chef Knife configuration file", "Comment": "Can contain references to Chef servers" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "proftpdpasswd", "Description": "cPanel backup ProFTPd credentials file", "Comment": "Contains usernames and password hashes for FTP accounts" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "robomongo.json", "Description": "Robomongo MongoDB manager configuration file", "Comment": "Can contain credentials for MongoDB databases" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "filezilla.xml", "Description": "FileZilla FTP configuration file", "Comment": "Can contain credentials for FTP servers" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "recentservers.xml", "Description": "FileZilla FTP recent servers file", "Comment": "Can contain credentials for FTP servers" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "ventrilo_srv.ini", "Description": "Ventrilo server configuration file", "Comment": "Can contain passwords" }, { - "Kind": "simple", "Part": "filename", "MatchOn": "terraform.tfvars", "Description": "Terraform variable config file", "Comment": "Can contain credentials for terraform providers" }, { - "Kind": "simple", "Part": "filename", "MatchOn": ".exports", "Description": "Shell configuration file", "Comment": "Shell configuration files can contain passwords, API keys, hostnames and other goodies" }, { - "Kind": "simple", "Part": "filename", "MatchOn": ".functions", "Description": "Shell configuration file", "Comment": "Shell configuration files can contain passwords, API keys, hostnames and other goodies" }, { - "Kind": "simple", "Part": "filename", "MatchOn": ".extra", "Description": "Shell configuration file", diff --git a/matching/filesignatures.go b/matching/filesignatures.go index c0efd84b..df0e98ae 100644 --- a/matching/filesignatures.go +++ b/matching/filesignatures.go @@ -1,5 +1,8 @@ package matching +import "regexp" + + type FileSignatureType struct { Extension string Filename string @@ -10,4 +13,35 @@ var fileSignatureTypes = FileSignatureType{ Extension: "extension", Filename: "filename", Path: "path", +} + +type FileSignature struct { + Part string + MatchOn string + Description string + Comment string + CompiledRegex *regexp.Regexp +} + +func (s FileSignature) Match(file MatchFile) bool { + var haystack string + switch s.Part { + case fileSignatureTypes.Path: + haystack = file.Path + case fileSignatureTypes.Filename: + haystack = file.Filename + case fileSignatureTypes.Extension: + haystack = file.Extension + default: + return false + } + return s.CompiledRegex.MatchString(haystack) +} + +func (s FileSignature) GetDescription() string { + return s.Description +} + +func (s FileSignature) GetComment() string { + return s.Comment } \ No newline at end of file diff --git a/matching/signatures.go b/matching/signatures.go index b31d12ff..713131d4 100644 --- a/matching/signatures.go +++ b/matching/signatures.go @@ -19,14 +19,6 @@ type ContentSignature struct { Comment string } -type FileSignature struct { - Kind string - Part string - MatchOn string - Description string - Comment string -} - func (s *Signatures) Load(mode int) error { fileSignaturePath := "./filesignatures.json" if !common.FileExists(fileSignaturePath) { @@ -41,26 +33,3 @@ func (s *Signatures) Load(mode int) error { return nil } -func (s FileSignature) Match(file MatchFile) bool { - var haystack *string - switch s.Part { - case fileSignatureTypes.Path: - haystack = &file.Path - case fileSignatureTypes.Filename: - haystack = &file.Filename - case fileSignatureTypes.Extension: - haystack = &file.Extension - default: - return false - } - - return s.MatchOn == *haystack -} - -func (s FileSignature) GetDescription() string { - return s.Description -} - -func (s FileSignature) GetComment() string { - return s.Comment -} From 43f26b00415a687b7286d59669956d9afc7e38d2 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 19 Mar 2020 17:24:53 -0400 Subject: [PATCH 087/226] remove unused interface implementation --- matching/patternfilesignature.go | 34 -------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 matching/patternfilesignature.go diff --git a/matching/patternfilesignature.go b/matching/patternfilesignature.go deleted file mode 100644 index 6e531a2f..00000000 --- a/matching/patternfilesignature.go +++ /dev/null @@ -1,34 +0,0 @@ -package matching - -import "regexp" - -type PatternFileSignature struct { - Part string - MatchOn *regexp.Regexp - Description string - Comment string -} - -func (s PatternFileSignature) Match(file MatchFile) bool { - var haystack *string - switch s.Part { - case fileSignatureTypes.Path: - haystack = &file.Path - case fileSignatureTypes.Filename: - haystack = &file.Filename - case fileSignatureTypes.Extension: - haystack = &file.Extension - default: - return false - } - - return s.MatchOn.MatchString(*haystack) -} - -func (s PatternFileSignature) GetDescription() string { - return s.Description -} - -func (s PatternFileSignature) GetComment() string { - return s.Comment -} From 763d76c01fad686876414296a0e3c8d9b99f8cb3 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Thu, 19 Mar 2020 17:25:44 -0400 Subject: [PATCH 088/226] move content signature concept --- matching/contentsignatures.go | 8 ++++++++ matching/signatures.go | 9 +-------- 2 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 matching/contentsignatures.go diff --git a/matching/contentsignatures.go b/matching/contentsignatures.go new file mode 100644 index 00000000..1b6af383 --- /dev/null +++ b/matching/contentsignatures.go @@ -0,0 +1,8 @@ +package matching + + +type ContentSignature struct { + MatchOn string + Description string + Comment string +} diff --git a/matching/signatures.go b/matching/signatures.go index 713131d4..5988cc9a 100644 --- a/matching/signatures.go +++ b/matching/signatures.go @@ -9,16 +9,10 @@ import ( ) type Signatures struct { - FileSignatures []FileSignature + FileSignatures []FileSignature ContentSignatures []ContentSignature } -type ContentSignature struct { - MatchOn string - Description string - Comment string -} - func (s *Signatures) Load(mode int) error { fileSignaturePath := "./filesignatures.json" if !common.FileExists(fileSignaturePath) { @@ -32,4 +26,3 @@ func (s *Signatures) Load(mode int) error { } return nil } - From e83c60630714493e09e1b1e40aa346fc19556828 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 20 Mar 2020 13:54:12 -0400 Subject: [PATCH 089/226] correct missed regex --- filesignatures.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filesignatures.json b/filesignatures.json index cdee3e5f..17248ccd 100644 --- a/filesignatures.json +++ b/filesignatures.json @@ -188,7 +188,7 @@ }, { "Part": "path", - "MatchOn": "", + "MatchOn": "\\.?gem/credentials$", "Description": "Rubygems credentials file", "Comment": "Can contain API key for a rubygems.org account" }, From f3012b02c5d9c1e2e75e89807fa2db0c1a653dc0 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 20 Mar 2020 13:55:55 -0400 Subject: [PATCH 090/226] match file signatures properly --- core/analysis.go | 8 ++++++-- core/session.go | 5 ----- matching/filesignatures.go | 21 ++++++++++++--------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index 07ed1a3b..2b7883e4 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "github.com/codeEmitter/gitrob/common" "github.com/codeEmitter/gitrob/github" "github.com/codeEmitter/gitrob/gitlab" @@ -170,8 +171,11 @@ func AnalyzeRepositories(sess *Session) { sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.CloneURL, matchFile.Path) for _, signature := range sess.Signatures.FileSignatures { - if signature.Match(matchFile) { - + matched, err := signature.Match(matchFile) + if err != nil { + sess.Out.Fatal(fmt.Sprintf("Error while performing match: %s", err)) + } + if matched { finding := &matching.Finding{ FilePath: path, Action: changeAction, diff --git a/core/session.go b/core/session.go index 6686fa66..a307e886 100644 --- a/core/session.go +++ b/core/session.go @@ -7,7 +7,6 @@ import ( "github.com/codeEmitter/gitrob/matching" "io/ioutil" "os" - "regexp" "runtime" "sync" "time" @@ -80,10 +79,6 @@ func (s *Session) Initialize() { func (s *Session) InitSignatures() { s.Signatures = matching.Signatures{} s.Signatures.Load(*s.Options.Mode) - for _, fileSignature := range s.Signatures.FileSignatures { - s.Out.Info(fileSignature.MatchOn) - fileSignature.CompiledRegex = regexp.MustCompile(fileSignature.MatchOn) - } } func (s *Session) Finish() { diff --git a/matching/filesignatures.go b/matching/filesignatures.go index df0e98ae..005beade 100644 --- a/matching/filesignatures.go +++ b/matching/filesignatures.go @@ -1,6 +1,10 @@ package matching -import "regexp" +import ( + "errors" + "fmt" + "regexp" +) type FileSignatureType struct { @@ -20,22 +24,21 @@ type FileSignature struct { MatchOn string Description string Comment string - CompiledRegex *regexp.Regexp } -func (s FileSignature) Match(file MatchFile) bool { - var haystack string +func (s FileSignature) Match(file MatchFile) (bool, error) { + var haystack *string switch s.Part { case fileSignatureTypes.Path: - haystack = file.Path + haystack = &file.Path case fileSignatureTypes.Filename: - haystack = file.Filename + haystack = &file.Filename case fileSignatureTypes.Extension: - haystack = file.Extension + haystack = &file.Extension default: - return false + return false, errors.New(fmt.Sprintf("Unrecognized 'Part' parameter: %s\n", s.Part)) } - return s.CompiledRegex.MatchString(haystack) + return regexp.MatchString(s.MatchOn, *haystack) } func (s FileSignature) GetDescription() string { From a0b9ee12224640c8504e3cc1099ec47b1452ea47 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 20 Mar 2020 16:00:47 -0400 Subject: [PATCH 091/226] only perform file analysis if mode is default (1), or scan content on file match (2) --- core/analysis.go | 68 +++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index 2b7883e4..d64fe95a 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -170,45 +170,47 @@ func AnalyzeRepositories(sess *Session) { } sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.CloneURL, matchFile.Path) - for _, signature := range sess.Signatures.FileSignatures { - matched, err := signature.Match(matchFile) - if err != nil { - sess.Out.Fatal(fmt.Sprintf("Error while performing match: %s", err)) - } - if matched { - finding := &matching.Finding{ - FilePath: path, - Action: changeAction, - Description: signature.GetDescription(), - Comment: signature.GetComment(), - RepositoryOwner: *repo.Owner, - RepositoryName: *repo.Name, - CommitHash: commit.Hash.String(), - CommitMessage: strings.TrimSpace(commit.Message), - CommitAuthor: commit.Author.String(), + if *sess.Options.Mode != 3 { + for _, signature := range sess.Signatures.FileSignatures { + matched, err := signature.Match(matchFile) + if err != nil { + sess.Out.Fatal(fmt.Sprintf("Error while performing match: %s", err)) } - finding.Initialize(sess.Github.AccessToken != "") - sess.AddFinding(finding) + if matched { + finding := &matching.Finding{ + FilePath: path, + Action: changeAction, + Description: signature.GetDescription(), + Comment: signature.GetComment(), + RepositoryOwner: *repo.Owner, + RepositoryName: *repo.Name, + CommitHash: commit.Hash.String(), + CommitMessage: strings.TrimSpace(commit.Message), + CommitAuthor: commit.Author.String(), + } + finding.Initialize(sess.Github.AccessToken != "") + sess.AddFinding(finding) - sess.Out.Warn(" %s: %s\n", strings.ToUpper(changeAction), finding.Description) - sess.Out.Info(" Path.......: %s\n", finding.FilePath) - sess.Out.Info(" Repo.......: %s\n", *repo.CloneURL) - sess.Out.Info(" Message....: %s\n", common.TruncateString(finding.CommitMessage, 100)) - sess.Out.Info(" Author.....: %s\n", finding.CommitAuthor) - if finding.Comment != "" { - sess.Out.Info(" Comment....: %s\n", finding.Comment) + sess.Out.Warn(" %s: %s\n", strings.ToUpper(changeAction), finding.Description) + sess.Out.Info(" Path.......: %s\n", finding.FilePath) + sess.Out.Info(" Repo.......: %s\n", *repo.CloneURL) + sess.Out.Info(" Message....: %s\n", common.TruncateString(finding.CommitMessage, 100)) + sess.Out.Info(" Author.....: %s\n", finding.CommitAuthor) + if finding.Comment != "" { + sess.Out.Info(" Comment....: %s\n", finding.Comment) + } + sess.Out.Info(" File URL...: %s\n", finding.FileUrl) + sess.Out.Info(" Commit URL.: %s\n", finding.CommitUrl) + sess.Out.Info(" ------------------------------------------------\n\n") + sess.Stats.IncrementFindings() + break } - sess.Out.Info(" File URL...: %s\n", finding.FileUrl) - sess.Out.Info(" Commit URL.: %s\n", finding.CommitUrl) - sess.Out.Info(" ------------------------------------------------\n\n") - sess.Stats.IncrementFindings() - break } + sess.Stats.IncrementFiles() } - sess.Stats.IncrementFiles() + sess.Stats.IncrementCommits() + sess.Out.Debug("[THREAD #%d][%s] Done analyzing changes in %s\n", tid, *repo.CloneURL, commit.Hash) } - sess.Stats.IncrementCommits() - sess.Out.Debug("[THREAD #%d][%s] Done analyzing changes in %s\n", tid, *repo.CloneURL, commit.Hash) } sess.Out.Debug("[THREAD #%d][%s] Done analyzing commits\n", tid, *repo.CloneURL) os.RemoveAll(path) From 81c6ae7bbcee5356377905f5202c7d82d6f22ada Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 20 Mar 2020 16:13:52 -0400 Subject: [PATCH 092/226] cleanup --- matching/interfaces.go | 7 ------- matching/simplefilesignature.go | 32 -------------------------------- 2 files changed, 39 deletions(-) delete mode 100644 matching/interfaces.go delete mode 100644 matching/simplefilesignature.go diff --git a/matching/interfaces.go b/matching/interfaces.go deleted file mode 100644 index d382168f..00000000 --- a/matching/interfaces.go +++ /dev/null @@ -1,7 +0,0 @@ -package matching - -type IFileSignature interface { - Match(file MatchFile) bool - GetDescription() string - GetComment() string -} diff --git a/matching/simplefilesignature.go b/matching/simplefilesignature.go deleted file mode 100644 index fc0a077e..00000000 --- a/matching/simplefilesignature.go +++ /dev/null @@ -1,32 +0,0 @@ -package matching - -type SimpleFileSignature struct { - Part string - MatchOn string - Description string - Comment string -} - -func (s SimpleFileSignature) Match(file MatchFile) bool { - var haystack *string - switch s.Part { - case fileSignatureTypes.Path: - haystack = &file.Path - case fileSignatureTypes.Filename: - haystack = &file.Filename - case fileSignatureTypes.Extension: - haystack = &file.Extension - default: - return false - } - - return s.MatchOn == *haystack -} - -func (s SimpleFileSignature) GetDescription() string { - return s.Description -} - -func (s SimpleFileSignature) GetComment() string { - return s.Comment -} From d4b6ef7e14d811cfafb3d9d314317eb6052bdcab Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Fri, 20 Mar 2020 16:31:21 -0400 Subject: [PATCH 093/226] cleanup / rename to setup for addition of content matching --- core/analysis.go | 67 +++++++++++++++++++------------------- matching/filesignatures.go | 2 +- matching/matchfile.go | 8 ++--- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index d64fe95a..f3fb6d17 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -163,48 +163,49 @@ func AnalyzeRepositories(sess *Session) { for _, change := range changes { changeAction := common.GetChangeAction(change) path := common.GetChangePath(change) - matchFile := matching.NewMatchFile(path) - if matchFile.IsSkippable() { - sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.CloneURL, matchFile.Path) + matchTarget := matching.NewMatchTarget(path) + if matchTarget.IsSkippable() { + sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.CloneURL, matchTarget.Path) continue } - sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.CloneURL, matchFile.Path) + sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.CloneURL, matchTarget.Path) if *sess.Options.Mode != 3 { - for _, signature := range sess.Signatures.FileSignatures { - matched, err := signature.Match(matchFile) + for _, fileSignature := range sess.Signatures.FileSignatures { + matched, err := fileSignature.Match(matchTarget) if err != nil { sess.Out.Fatal(fmt.Sprintf("Error while performing match: %s", err)) } - if matched { - finding := &matching.Finding{ - FilePath: path, - Action: changeAction, - Description: signature.GetDescription(), - Comment: signature.GetComment(), - RepositoryOwner: *repo.Owner, - RepositoryName: *repo.Name, - CommitHash: commit.Hash.String(), - CommitMessage: strings.TrimSpace(commit.Message), - CommitAuthor: commit.Author.String(), - } - finding.Initialize(sess.Github.AccessToken != "") - sess.AddFinding(finding) + if !matched { + continue + } + finding := &matching.Finding{ + FilePath: path, + Action: changeAction, + Description: fileSignature.GetDescription(), + Comment: fileSignature.GetComment(), + RepositoryOwner: *repo.Owner, + RepositoryName: *repo.Name, + CommitHash: commit.Hash.String(), + CommitMessage: strings.TrimSpace(commit.Message), + CommitAuthor: commit.Author.String(), + } + finding.Initialize(sess.Github.AccessToken != "") + sess.AddFinding(finding) - sess.Out.Warn(" %s: %s\n", strings.ToUpper(changeAction), finding.Description) - sess.Out.Info(" Path.......: %s\n", finding.FilePath) - sess.Out.Info(" Repo.......: %s\n", *repo.CloneURL) - sess.Out.Info(" Message....: %s\n", common.TruncateString(finding.CommitMessage, 100)) - sess.Out.Info(" Author.....: %s\n", finding.CommitAuthor) - if finding.Comment != "" { - sess.Out.Info(" Comment....: %s\n", finding.Comment) - } - sess.Out.Info(" File URL...: %s\n", finding.FileUrl) - sess.Out.Info(" Commit URL.: %s\n", finding.CommitUrl) - sess.Out.Info(" ------------------------------------------------\n\n") - sess.Stats.IncrementFindings() - break + sess.Out.Warn(" %s: %s\n", strings.ToUpper(changeAction), finding.Description) + sess.Out.Info(" Path.......: %s\n", finding.FilePath) + sess.Out.Info(" Repo.......: %s\n", *repo.CloneURL) + sess.Out.Info(" Message....: %s\n", common.TruncateString(finding.CommitMessage, 100)) + sess.Out.Info(" Author.....: %s\n", finding.CommitAuthor) + if finding.Comment != "" { + sess.Out.Info(" Comment....: %s\n", finding.Comment) } + sess.Out.Info(" File URL...: %s\n", finding.FileUrl) + sess.Out.Info(" Commit URL.: %s\n", finding.CommitUrl) + sess.Out.Info(" ------------------------------------------------\n\n") + sess.Stats.IncrementFindings() + break } sess.Stats.IncrementFiles() } diff --git a/matching/filesignatures.go b/matching/filesignatures.go index 005beade..896a599e 100644 --- a/matching/filesignatures.go +++ b/matching/filesignatures.go @@ -26,7 +26,7 @@ type FileSignature struct { Comment string } -func (s FileSignature) Match(file MatchFile) (bool, error) { +func (s FileSignature) Match(file MatchTarget) (bool, error) { var haystack *string switch s.Part { case fileSignatureTypes.Path: diff --git a/matching/matchfile.go b/matching/matchfile.go index 25963987..b926e3de 100644 --- a/matching/matchfile.go +++ b/matching/matchfile.go @@ -5,7 +5,7 @@ import ( "strings" ) -type MatchFile struct { +type MatchTarget struct { Path string Filename string Extension string @@ -14,7 +14,7 @@ type MatchFile struct { var skippableExtensions = []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".tif", ".psd", ".xcf"} var skippablePathIndicators = []string{"node_modules/", "vendor/bundle", "vendor/cache"} -func (f *MatchFile) IsSkippable() bool { +func (f *MatchTarget) IsSkippable() bool { ext := strings.ToLower(f.Extension) path := strings.ToLower(f.Path) for _, skippableExt := range skippableExtensions { @@ -30,10 +30,10 @@ func (f *MatchFile) IsSkippable() bool { return false } -func NewMatchFile(path string) MatchFile { +func NewMatchTarget(path string) MatchTarget { _, filename := filepath.Split(path) extension := filepath.Ext(path) - return MatchFile{ + return MatchTarget{ Path: path, Filename: filename, Extension: extension, From 305dd336a695b1699fd538a41da183d1b37475a2 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 23 Mar 2020 15:22:17 -0400 Subject: [PATCH 094/226] first pass at grabbing content from a commit from all the chunks in each file --- common/sourcecontrol.go | 17 +++++++++++++++++ core/analysis.go | 7 ++++++- matching/matchfile.go | 4 +++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/common/sourcecontrol.go b/common/sourcecontrol.go index d1fb4a12..843202cb 100644 --- a/common/sourcecontrol.go +++ b/common/sourcecontrol.go @@ -134,3 +134,20 @@ func GetChangePath(change *object.Change) string { return change.To.Name } } + +func GetChangeContent(change *object.Change) (string, error) { + patch, err := change.Patch() + if err != nil { + return "", err + } + result := "" + for _, filePatch := range patch.FilePatches() { + if filePatch.IsBinary() { + continue + } + for _, chunk := range filePatch.Chunks() { + result += chunk.Content() + } + } + return result, nil +} diff --git a/core/analysis.go b/core/analysis.go index f3fb6d17..65e0b0b9 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -163,7 +163,12 @@ func AnalyzeRepositories(sess *Session) { for _, change := range changes { changeAction := common.GetChangeAction(change) path := common.GetChangePath(change) - matchTarget := matching.NewMatchTarget(path) + content, err := common.GetChangeContent(change) + if err != nil { + sess.Out.Fatal(fmt.Sprintf("Error retrieving content from commit: %s", change.To.Name)) + } + matchTarget := matching.NewMatchTarget(path, content) + //how do i get content of the commit `git log if matchTarget.IsSkippable() { sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.CloneURL, matchTarget.Path) continue diff --git a/matching/matchfile.go b/matching/matchfile.go index b926e3de..016cb839 100644 --- a/matching/matchfile.go +++ b/matching/matchfile.go @@ -9,6 +9,7 @@ type MatchTarget struct { Path string Filename string Extension string + Content string } var skippableExtensions = []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".tif", ".psd", ".xcf"} @@ -30,12 +31,13 @@ func (f *MatchTarget) IsSkippable() bool { return false } -func NewMatchTarget(path string) MatchTarget { +func NewMatchTarget(path string, content string) MatchTarget { _, filename := filepath.Split(path) extension := filepath.Ext(path) return MatchTarget{ Path: path, Filename: filename, Extension: extension, + Content: content, } } From f67cc67986b2fe08747a0dfbb14a2ef65c3afdba Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 23 Mar 2020 17:19:58 -0400 Subject: [PATCH 095/226] add basic aws content signatures --- contentsignatures.json | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 contentsignatures.json diff --git a/contentsignatures.json b/contentsignatures.json new file mode 100644 index 00000000..2fc66864 --- /dev/null +++ b/contentsignatures.json @@ -0,0 +1,29 @@ +{ + "ContentSignatures": [ + { + "MatchOn": "aws_secret_access_key.*?[a-zA-Z0-9/\\\\+]{40}", + "Description": "AWS Secret Key", + "Comment": "" + }, + { + "MatchOn": "(? Date: Mon, 23 Mar 2020 17:30:12 -0400 Subject: [PATCH 096/226] load signatures depending on mode --- matching/signatures.go | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/matching/signatures.go b/matching/signatures.go index 5988cc9a..25b766cf 100644 --- a/matching/signatures.go +++ b/matching/signatures.go @@ -13,16 +13,33 @@ type Signatures struct { ContentSignatures []ContentSignature } -func (s *Signatures) Load(mode int) error { - fileSignaturePath := "./filesignatures.json" - if !common.FileExists(fileSignaturePath) { - return errors.New(fmt.Sprintf("Missing signature file: %s.\n", fileSignaturePath)) +func (s *Signatures) loadSignatures(path string) error { + if !common.FileExists(path) { + return errors.New(fmt.Sprintf("Missing signature file: %s.\n", path)) + } + data, readError := ioutil.ReadFile(path) + if readError != nil { + return readError } - data, err := ioutil.ReadFile(fileSignaturePath); if err != nil { - return err + if unmarshalError := json.Unmarshal(data, &s); unmarshalError != nil { + return unmarshalError + } + return nil +} + +func (s *Signatures) Load(mode int) error { + var e error + if mode != 3 { + e = s.loadSignatures("./filesignatures.json") + if e != nil { + return e + } } - if err2 := json.Unmarshal(data, &s); err2 != nil { - return err2 + if mode != 1 { + e = s.loadSignatures("./contentsignatures.json") + if e != nil { + return e + } } return nil } From c6757e5a77e602d2e5b7852b1e1e667d842af5c6 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Mon, 23 Mar 2020 17:31:30 -0400 Subject: [PATCH 097/226] WIP - search change content for secrets --- core/analysis.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index 65e0b0b9..3fe5fe06 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -163,11 +163,7 @@ func AnalyzeRepositories(sess *Session) { for _, change := range changes { changeAction := common.GetChangeAction(change) path := common.GetChangePath(change) - content, err := common.GetChangeContent(change) - if err != nil { - sess.Out.Fatal(fmt.Sprintf("Error retrieving content from commit: %s", change.To.Name)) - } - matchTarget := matching.NewMatchTarget(path, content) + matchTarget := matching.NewMatchTarget(path, "") //how do i get content of the commit `git log if matchTarget.IsSkippable() { sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.CloneURL, matchTarget.Path) @@ -184,6 +180,11 @@ func AnalyzeRepositories(sess *Session) { if !matched { continue } + //if mode == 2 + matchTarget.Content, err = common.GetChangeContent(change) + if err != nil { + sess.Out.Fatal(fmt.Sprintf("Error retrieving content in commit %s, change %s.", commit.String(), change.String())) + } finding := &matching.Finding{ FilePath: path, Action: changeAction, From f82c9113eaf72f24518114f3e6325736a2f44517 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 11:10:02 -0400 Subject: [PATCH 098/226] rename/cleanup --- matching/filesignatures.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/matching/filesignatures.go b/matching/filesignatures.go index 896a599e..954e90f9 100644 --- a/matching/filesignatures.go +++ b/matching/filesignatures.go @@ -26,25 +26,25 @@ type FileSignature struct { Comment string } -func (s FileSignature) Match(file MatchTarget) (bool, error) { +func (f FileSignature) Match(target MatchTarget) (bool, error) { var haystack *string - switch s.Part { + switch f.Part { case fileSignatureTypes.Path: - haystack = &file.Path + haystack = &target.Path case fileSignatureTypes.Filename: - haystack = &file.Filename + haystack = &target.Filename case fileSignatureTypes.Extension: - haystack = &file.Extension + haystack = &target.Extension default: - return false, errors.New(fmt.Sprintf("Unrecognized 'Part' parameter: %s\n", s.Part)) + return false, errors.New(fmt.Sprintf("Unrecognized 'Part' parameter: %f\n", f.Part)) } - return regexp.MatchString(s.MatchOn, *haystack) + return regexp.MatchString(f.MatchOn, *haystack) } -func (s FileSignature) GetDescription() string { - return s.Description +func (f FileSignature) GetDescription() string { + return f.Description } -func (s FileSignature) GetComment() string { - return s.Comment +func (f FileSignature) GetComment() string { + return f.Comment } \ No newline at end of file From 694b6532152bc195e644038fc48ae10db0ae360b Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 11:10:23 -0400 Subject: [PATCH 099/226] output details of loaded signatures on launch --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 84d6647e..2ae34e8f 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ func main() { sess.Out.Info("%s\n\n", common.ASCIIBanner) sess.Out.Important("%s v%s started at %s\n", common.Name, common.Version, sess.Stats.StartedAt.Format(time.RFC3339)) - sess.Out.Important("Loaded %d signatures\n", len(sess.Signatures.FileSignatures) + len(sess.Signatures.ContentSignatures)) + sess.Out.Important("Loaded %d file signatures and %d content signatures.\n", len(sess.Signatures.FileSignatures), len(sess.Signatures.ContentSignatures)) sess.Out.Important("Web interface available at http://%s:%d\n", *sess.Options.BindAddress, *sess.Options.Port) if sess.Stats.Status == "finished" { From a38f1bd9489f26764cddf57b42fc01d8e0f40cef Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 11:11:12 -0400 Subject: [PATCH 100/226] add content signature recievers for matching --- matching/contentsignatures.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/matching/contentsignatures.go b/matching/contentsignatures.go index 1b6af383..a43b7a53 100644 --- a/matching/contentsignatures.go +++ b/matching/contentsignatures.go @@ -1,8 +1,21 @@ package matching +import "regexp" type ContentSignature struct { MatchOn string Description string Comment string } + +func (c ContentSignature) Match(target MatchTarget) (bool, error) { + return regexp.MatchString(c.MatchOn, target.Content) +} + +func (c ContentSignature) GetDescription() string { + return c.Description +} + +func (c ContentSignature) GetComment() string { + return c.Comment +} \ No newline at end of file From 856ff23e97eee0a037162f566522121f36719d2d Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 11:40:33 -0400 Subject: [PATCH 101/226] throw on error loading signatures --- core/session.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/session.go b/core/session.go index a307e886..a9009bad 100644 --- a/core/session.go +++ b/core/session.go @@ -78,7 +78,10 @@ func (s *Session) Initialize() { func (s *Session) InitSignatures() { s.Signatures = matching.Signatures{} - s.Signatures.Load(*s.Options.Mode) + err := s.Signatures.Load(*s.Options.Mode) + if err != nil { + s.Out.Fatal("Error loading signatures: %s\n", err) + } } func (s *Session) Finish() { From 4768bdaa2db589973b667f034ee948b12f556aef Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 11:45:10 -0400 Subject: [PATCH 102/226] set a session type bool instead of string checking all over the place --- core/router.go | 6 ++---- core/session.go | 28 +++++++++++++++------------- main.go | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/core/router.go b/core/router.go index 3429616e..d93a7e9b 100644 --- a/core/router.go +++ b/core/router.go @@ -21,7 +21,7 @@ const ( ReferrerPolicy = "no-referrer" ) -var IsGithub = true +var IsGithub bool type binaryFileSystem struct { fs http.FileSystem @@ -50,7 +50,7 @@ func BinaryFileSystem(root string) *binaryFileSystem { func NewRouter(s *Session) *gin.Engine { - IsGithub = s.Github.AccessToken != "" + IsGithub = s.IsGithubSession if *s.Options.Debug == true { gin.SetMode(gin.DebugMode) @@ -87,8 +87,6 @@ func NewRouter(s *Session) *gin.Engine { } func fetchFile(c *gin.Context) { - //Github: https://raw.githubusercontent.com/mhh/config/6257553245337bdae802a8e19935e8cdab562bad/bash/.bashrc - //GitLab: https://gitlab.com/pharrison/python-gitlab/-/raw/ab7d794251bcdbafce69b1bde0628cd3b710d784/docs/gl_objects/settings.py fileUrl := func() string { if IsGithub { return fmt.Sprintf("%s/%s/%s/%s%s", GithubBaseUri, c.Param("owner"), c.Param("repo"), c.Param("commit"), c.Param("path")) diff --git a/core/session.go b/core/session.go index a9009bad..90e70dc0 100644 --- a/core/session.go +++ b/core/session.go @@ -51,18 +51,19 @@ type GitLab struct { type Session struct { sync.Mutex - Version string - Options Options `json:"-"` //do not unmarshal to json on save - Out *Logger `json:"-"` //do not unmarshal to json on save - Stats *Stats - Github Github `json:"-"` //do not unmarshal to json on save - GitLab GitLab `json:"-"` //do not unmarshal to json on save - Client common.IClient `json:"-"` //do not unmarshal to json on save - Router *gin.Engine `json:"-"` //do not unmarshal to json on save - Targets []*common.Owner - Repositories []*common.Repository - Findings []*matching.Finding - Signatures matching.Signatures + Version string + Options Options `json:"-"` //do not unmarshal to json on save + Out *Logger `json:"-"` //do not unmarshal to json on save + Stats *Stats + Github Github `json:"-"` //do not unmarshal to json on save + GitLab GitLab `json:"-"` //do not unmarshal to json on save + Client common.IClient `json:"-"` //do not unmarshal to json on save + Router *gin.Engine `json:"-"` //do not unmarshal to json on save + Targets []*common.Owner + Repositories []*common.Repository + Findings []*matching.Finding + IsGithubSession bool + Signatures matching.Signatures } func (s *Session) Initialize() { @@ -161,10 +162,11 @@ func (s *Session) ValidateTokenConfig() { s.Out.Fatal("No valid API token was found.\n") } } + s.IsGithubSession = s.Github.AccessToken != "" } func (s *Session) InitAPIClient() { - if s.Github.AccessToken != "" { + if s.IsGithubSession { s.Client = gh.Client.NewClient(gh.Client{}, s.Github.AccessToken) } else { s.Client = gl.Client.NewClient(gl.Client{}, s.GitLab.AccessToken) diff --git a/main.go b/main.go index 2ae34e8f..30075f88 100644 --- a/main.go +++ b/main.go @@ -30,7 +30,7 @@ func main() { } else { if len(sess.Options.Logins) == 0 { host := func() string { - if sess.Github.AccessToken != "" { + if sess.IsGithubSession { return "Github organization" } else { return "GitLab group" From 48e70d0889b79961db294873cc688a2751741427 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 12:36:07 -0400 Subject: [PATCH 103/226] =?UTF-8?q?substitute=20regexes=20for=20some=20aws?= =?UTF-8?q?=20matches=20since=20perl=20syntax=20isn=E2=80=99t=20supported?= =?UTF-8?q?=20in=20Golang?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contentsignatures.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contentsignatures.json b/contentsignatures.json index 2fc66864..dae08344 100644 --- a/contentsignatures.json +++ b/contentsignatures.json @@ -6,12 +6,12 @@ "Comment": "" }, { - "MatchOn": "(? Date: Tue, 24 Mar 2020 12:39:51 -0400 Subject: [PATCH 104/226] WIP perform content matching in mode 2 --- core/analysis.go | 77 +++++++++++++++++++++++++------------------- core/session.go | 13 ++++++++ matching/findings.go | 1 + 3 files changed, 58 insertions(+), 33 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index 3fe5fe06..3f905433 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -7,6 +7,7 @@ import ( "github.com/codeEmitter/gitrob/gitlab" "github.com/codeEmitter/gitrob/matching" "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing/object" "os" "strings" "sync" @@ -92,6 +93,27 @@ func GatherRepositories(sess *Session) { wg.Wait() } +func createFinding(repo common.Repository, + commit object.Commit, + change *object.Change, + fileSignature matching.FileSignature, + isGitHubSession bool) *matching.Finding { + finding := &matching.Finding{ + FilePath: common.GetChangePath(change), + Action: common.GetChangeAction(change), + Description: fileSignature.GetDescription(), + Comment: fileSignature.GetComment(), + RepositoryOwner: *repo.Owner, + RepositoryName: *repo.Name, + CommitHash: commit.Hash.String(), + CommitMessage: strings.TrimSpace(commit.Message), + CommitAuthor: commit.Author.String(), + CloneUrl: *repo.CloneURL, + } + finding.Initialize(isGitHubSession) + return finding +} + func AnalyzeRepositories(sess *Session) { sess.Stats.Status = StatusAnalyzing var ch = make(chan *common.Repository, len(sess.Repositories)) @@ -128,7 +150,7 @@ func AnalyzeRepositories(sess *Session) { Depth: sess.Options.CommitDepth, Token: &sess.GitLab.AccessToken, } - if sess.Github.AccessToken != "" { + if sess.IsGithubSession { return github.CloneRepository(&cloneConfig) } else { userName := "oauth2" @@ -161,10 +183,8 @@ func AnalyzeRepositories(sess *Session) { changes, _ := common.GetChanges(commit, clone) sess.Out.Debug("[THREAD #%d][%s] Changes in %s: %d\n", tid, *repo.CloneURL, commit.Hash, len(changes)) for _, change := range changes { - changeAction := common.GetChangeAction(change) path := common.GetChangePath(change) matchTarget := matching.NewMatchTarget(path, "") - //how do i get content of the commit `git log if matchTarget.IsSkippable() { sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.CloneURL, matchTarget.Path) continue @@ -175,42 +195,33 @@ func AnalyzeRepositories(sess *Session) { for _, fileSignature := range sess.Signatures.FileSignatures { matched, err := fileSignature.Match(matchTarget) if err != nil { - sess.Out.Fatal(fmt.Sprintf("Error while performing match: %s", err)) + sess.Out.Fatal(fmt.Sprintf("Error while performing file match: %s", err)) } if !matched { continue } - //if mode == 2 - matchTarget.Content, err = common.GetChangeContent(change) - if err != nil { - sess.Out.Fatal(fmt.Sprintf("Error retrieving content in commit %s, change %s.", commit.String(), change.String())) - } - finding := &matching.Finding{ - FilePath: path, - Action: changeAction, - Description: fileSignature.GetDescription(), - Comment: fileSignature.GetComment(), - RepositoryOwner: *repo.Owner, - RepositoryName: *repo.Name, - CommitHash: commit.Hash.String(), - CommitMessage: strings.TrimSpace(commit.Message), - CommitAuthor: commit.Author.String(), + if *sess.Options.Mode == 1 { + finding := createFinding(*repo, *commit, change, fileSignature, sess.IsGithubSession) + sess.AddFinding(finding) } - finding.Initialize(sess.Github.AccessToken != "") - sess.AddFinding(finding) - - sess.Out.Warn(" %s: %s\n", strings.ToUpper(changeAction), finding.Description) - sess.Out.Info(" Path.......: %s\n", finding.FilePath) - sess.Out.Info(" Repo.......: %s\n", *repo.CloneURL) - sess.Out.Info(" Message....: %s\n", common.TruncateString(finding.CommitMessage, 100)) - sess.Out.Info(" Author.....: %s\n", finding.CommitAuthor) - if finding.Comment != "" { - sess.Out.Info(" Comment....: %s\n", finding.Comment) + if *sess.Options.Mode == 2 { + sess.Out.Debug("[THREAD #%d][%s] Matching content: %s...\n", tid, *repo.CloneURL, matchTarget.Content) + matchTarget.Content, err = common.GetChangeContent(change) + if err != nil { + sess.Out.Fatal(fmt.Sprintf("Error retrieving content in commit %s, change %s.", commit.String(), change.String())) + } + for _, contentSignature := range sess.Signatures.ContentSignatures { + matched, err := contentSignature.Match(matchTarget) + if err != nil { + sess.Out.Fatal(fmt.Sprintf("Error while performing content match: %s", err)) + } + if !matched { + continue + } + finding := createFinding(*repo, *commit, change, fileSignature, sess.IsGithubSession) + sess.AddFinding(finding) + } } - sess.Out.Info(" File URL...: %s\n", finding.FileUrl) - sess.Out.Info(" Commit URL.: %s\n", finding.CommitUrl) - sess.Out.Info(" ------------------------------------------------\n\n") - sess.Stats.IncrementFindings() break } sess.Stats.IncrementFiles() diff --git a/core/session.go b/core/session.go index 90e70dc0..a7e89113 100644 --- a/core/session.go +++ b/core/session.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "os" "runtime" + "strings" "sync" "time" @@ -116,6 +117,18 @@ func (s *Session) AddFinding(finding *matching.Finding) { s.Lock() defer s.Unlock() s.Findings = append(s.Findings, finding) + s.Out.Warn(" %s: %s\n", strings.ToUpper(finding.Action), finding.Description) + s.Out.Info(" Path.......: %s\n", finding.FilePath) + s.Out.Info(" Repo.......: %s\n", finding.CloneUrl) + s.Out.Info(" Message....: %s\n", common.TruncateString(finding.CommitMessage, 100)) + s.Out.Info(" Author.....: %s\n", finding.CommitAuthor) + if finding.Comment != "" { + s.Out.Info(" Comment....: %s\n", finding.Comment) + } + s.Out.Info(" File URL...: %s\n", finding.FileUrl) + s.Out.Info(" Commit URL.: %s\n", finding.CommitUrl) + s.Out.Info(" ------------------------------------------------\n\n") + s.Stats.IncrementFindings() } func (s *Session) InitStats() { diff --git a/matching/findings.go b/matching/findings.go index f4c8627f..baef7646 100644 --- a/matching/findings.go +++ b/matching/findings.go @@ -21,6 +21,7 @@ type Finding struct { FileUrl string CommitUrl string RepositoryUrl string + CloneUrl string } func (f *Finding) setupUrls(isGithubSession bool) { From b7eda27a28b7a202a0693496f37c7fc9e922331a Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 13:58:38 -0400 Subject: [PATCH 105/226] factor out a function for matching content --- core/analysis.go | 80 +++++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index 3f905433..8d996b17 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -114,6 +114,33 @@ func createFinding(repo common.Repository, return finding } +func matchContent(sess *Session, + matchTarget matching.MatchTarget, + repo common.Repository, + change *object.Change, + commit object.Commit, + fileSignature matching.FileSignature, + threadId int) { + + content, err := common.GetChangeContent(change) + if err != nil { + sess.Out.Fatal("Error retrieving content in commit %s, change %s.", commit.String(), change.String()) + } + matchTarget.Content = content + sess.Out.Debug("[THREAD #%d][%s] Matching content in %s...\n", threadId, *repo.CloneURL, commit.Hash) + for _, contentSignature := range sess.Signatures.ContentSignatures { + matched, err := contentSignature.Match(matchTarget) + if err != nil { + sess.Out.Fatal("Error while performing content match: %s", err) + } + if !matched { + continue + } + finding := createFinding(repo, commit, change, fileSignature, sess.IsGithubSession) + sess.AddFinding(finding) + } +} + func AnalyzeRepositories(sess *Session) { sess.Stats.Status = StatusAnalyzing var ch = make(chan *common.Repository, len(sess.Repositories)) @@ -191,44 +218,27 @@ func AnalyzeRepositories(sess *Session) { } sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.CloneURL, matchTarget.Path) - if *sess.Options.Mode != 3 { - for _, fileSignature := range sess.Signatures.FileSignatures { - matched, err := fileSignature.Match(matchTarget) - if err != nil { - sess.Out.Fatal(fmt.Sprintf("Error while performing file match: %s", err)) - } - if !matched { - continue - } - if *sess.Options.Mode == 1 { - finding := createFinding(*repo, *commit, change, fileSignature, sess.IsGithubSession) - sess.AddFinding(finding) - } - if *sess.Options.Mode == 2 { - sess.Out.Debug("[THREAD #%d][%s] Matching content: %s...\n", tid, *repo.CloneURL, matchTarget.Content) - matchTarget.Content, err = common.GetChangeContent(change) - if err != nil { - sess.Out.Fatal(fmt.Sprintf("Error retrieving content in commit %s, change %s.", commit.String(), change.String())) - } - for _, contentSignature := range sess.Signatures.ContentSignatures { - matched, err := contentSignature.Match(matchTarget) - if err != nil { - sess.Out.Fatal(fmt.Sprintf("Error while performing content match: %s", err)) - } - if !matched { - continue - } - finding := createFinding(*repo, *commit, change, fileSignature, sess.IsGithubSession) - sess.AddFinding(finding) - } - } - break + for _, fileSignature := range sess.Signatures.FileSignatures { + matched, err := fileSignature.Match(matchTarget) + if err != nil { + sess.Out.Fatal(fmt.Sprintf("Error while performing file match: %s", err)) + } + if !matched { + continue + } + if *sess.Options.Mode == 1 { + finding := createFinding(*repo, *commit, change, fileSignature, sess.IsGithubSession) + sess.AddFinding(finding) + } + if *sess.Options.Mode == 2 { + matchContent(sess, matchTarget, *repo, change, *commit, fileSignature, tid) } - sess.Stats.IncrementFiles() + break } - sess.Stats.IncrementCommits() - sess.Out.Debug("[THREAD #%d][%s] Done analyzing changes in %s\n", tid, *repo.CloneURL, commit.Hash) + sess.Stats.IncrementFiles() } + sess.Stats.IncrementCommits() + sess.Out.Debug("[THREAD #%d][%s] Done analyzing changes in %s\n", tid, *repo.CloneURL, commit.Hash) } sess.Out.Debug("[THREAD #%d][%s] Done analyzing commits\n", tid, *repo.CloneURL) os.RemoveAll(path) From 4febd257e3c390be8956de52dd535f1632ff2f79 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 14:47:55 -0400 Subject: [PATCH 106/226] factor out method for matching on file --- core/analysis.go | 62 ++++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index 8d996b17..e7dbbd70 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -141,6 +141,37 @@ func matchContent(sess *Session, } } +func matchFile(sess *Session, repo *common.Repository, commit *object.Commit, changes object.Changes, threadId int) { + for _, change := range changes { + path := common.GetChangePath(change) + matchTarget := matching.NewMatchTarget(path, "") + if matchTarget.IsSkippable() { + sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", threadId, *repo.CloneURL, matchTarget.Path) + continue + } + sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", threadId, *repo.CloneURL, matchTarget.Path) + + for _, fileSignature := range sess.Signatures.FileSignatures { + matched, err := fileSignature.Match(matchTarget) + if err != nil { + sess.Out.Fatal(fmt.Sprintf("Error while performing file match: %s", err)) + } + if !matched { + continue + } + if *sess.Options.Mode == 1 { + finding := createFinding(*repo, *commit, change, fileSignature, sess.IsGithubSession) + sess.AddFinding(finding) + } + if *sess.Options.Mode == 2 { + matchContent(sess, matchTarget, *repo, change, *commit, fileSignature, threadId) + } + break + } + sess.Stats.IncrementFiles() + } +} + func AnalyzeRepositories(sess *Session) { sess.Stats.Status = StatusAnalyzing var ch = make(chan *common.Repository, len(sess.Repositories)) @@ -208,35 +239,10 @@ func AnalyzeRepositories(sess *Session) { for _, commit := range history { sess.Out.Debug("[THREAD #%d][%s] Analyzing commit: %s\n", tid, *repo.CloneURL, commit.Hash) changes, _ := common.GetChanges(commit, clone) - sess.Out.Debug("[THREAD #%d][%s] Changes in %s: %d\n", tid, *repo.CloneURL, commit.Hash, len(changes)) - for _, change := range changes { - path := common.GetChangePath(change) - matchTarget := matching.NewMatchTarget(path, "") - if matchTarget.IsSkippable() { - sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", tid, *repo.CloneURL, matchTarget.Path) - continue - } - sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", tid, *repo.CloneURL, matchTarget.Path) + sess.Out.Debug("[THREAD #%d][%s] %d changes in %s\n", tid, *repo.CloneURL, commit.Hash, len(changes)) + + matchFile(sess, repo, commit, changes, tid) - for _, fileSignature := range sess.Signatures.FileSignatures { - matched, err := fileSignature.Match(matchTarget) - if err != nil { - sess.Out.Fatal(fmt.Sprintf("Error while performing file match: %s", err)) - } - if !matched { - continue - } - if *sess.Options.Mode == 1 { - finding := createFinding(*repo, *commit, change, fileSignature, sess.IsGithubSession) - sess.AddFinding(finding) - } - if *sess.Options.Mode == 2 { - matchContent(sess, matchTarget, *repo, change, *commit, fileSignature, tid) - } - break - } - sess.Stats.IncrementFiles() - } sess.Stats.IncrementCommits() sess.Out.Debug("[THREAD #%d][%s] Done analyzing changes in %s\n", tid, *repo.CloneURL, commit.Hash) } From f519edf2ba4e11003651e31f9ab7105ca73a9ac2 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 15:07:50 -0400 Subject: [PATCH 107/226] factor out a method for cloning repos --- core/analysis.go | 59 ++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index e7dbbd70..0da21435 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -172,6 +172,39 @@ func matchFile(sess *Session, repo *common.Repository, commit *object.Commit, ch } } +func cloneRepository(sess *Session, repo *common.Repository, threadId int) (*git.Repository, string, error) { + sess.Out.Debug("[THREAD #%d][%s] Cloning repository...\n", threadId, *repo.CloneURL) + + cloneConfig := common.CloneConfiguration{ + Url: repo.CloneURL, + Branch: repo.DefaultBranch, + Depth: sess.Options.CommitDepth, + Token: &sess.GitLab.AccessToken, + } + + var clone *git.Repository + var path string + var err error + + if sess.IsGithubSession { + clone, path, err = github.CloneRepository(&cloneConfig) + } else { + userName := "oauth2" + cloneConfig.Username = &userName + clone, path, err = gitlab.CloneRepository(&cloneConfig) + } + if err != nil { + if err.Error() != "remote repository is empty" { + sess.Out.Error("Error cloning repository %s: %s\n", *repo.CloneURL, err) + } + sess.Stats.IncrementRepositories() + sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) + return nil, "", nil + } + sess.Out.Debug("[THREAD #%d][%s] Cloned repository to: %s\n", threadId, *repo.CloneURL, path) + return clone, path, err +} + func AnalyzeRepositories(sess *Session) { sess.Stats.Status = StatusAnalyzing var ch = make(chan *common.Repository, len(sess.Repositories)) @@ -200,31 +233,7 @@ func AnalyzeRepositories(sess *Session) { return } - sess.Out.Debug("[THREAD #%d][%s] Cloning repository...\n", tid, *repo.CloneURL) - clone, path, err := func() (*git.Repository, string, error) { - cloneConfig := common.CloneConfiguration{ - Url: repo.CloneURL, - Branch: repo.DefaultBranch, - Depth: sess.Options.CommitDepth, - Token: &sess.GitLab.AccessToken, - } - if sess.IsGithubSession { - return github.CloneRepository(&cloneConfig) - } else { - userName := "oauth2" - cloneConfig.Username = &userName - return gitlab.CloneRepository(&cloneConfig) - } - }() - if err != nil { - if err.Error() != "remote repository is empty" { - sess.Out.Error("Error cloning repository %s: %s\n", *repo.CloneURL, err) - } - sess.Stats.IncrementRepositories() - sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) - continue - } - sess.Out.Debug("[THREAD #%d][%s] Cloned repository to: %s\n", tid, *repo.CloneURL, path) + clone, path, err := cloneRepository(sess, repo, tid) history, err := common.GetRepositoryHistory(clone) if err != nil { From b47e67c306a557fd965cffb9c94bb077696561da Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 15:08:07 -0400 Subject: [PATCH 108/226] fix logging --- core/analysis.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/analysis.go b/core/analysis.go index 0da21435..6136c5f6 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -248,7 +248,7 @@ func AnalyzeRepositories(sess *Session) { for _, commit := range history { sess.Out.Debug("[THREAD #%d][%s] Analyzing commit: %s\n", tid, *repo.CloneURL, commit.Hash) changes, _ := common.GetChanges(commit, clone) - sess.Out.Debug("[THREAD #%d][%s] %d changes in %s\n", tid, *repo.CloneURL, commit.Hash, len(changes)) + sess.Out.Debug("[THREAD #%d][%s] %s changes in %d\n", tid, *repo.CloneURL, commit.Hash, len(changes)) matchFile(sess, repo, commit, changes, tid) From 7d0b364e519c8970e45276d3fb25960ed8f48e30 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 15:08:15 -0400 Subject: [PATCH 109/226] rename method --- core/analysis.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index 6136c5f6..82c340c6 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -141,7 +141,7 @@ func matchContent(sess *Session, } } -func matchFile(sess *Session, repo *common.Repository, commit *object.Commit, changes object.Changes, threadId int) { +func findSecrets(sess *Session, repo *common.Repository, commit *object.Commit, changes object.Changes, threadId int) { for _, change := range changes { path := common.GetChangePath(change) matchTarget := matching.NewMatchTarget(path, "") @@ -250,7 +250,7 @@ func AnalyzeRepositories(sess *Session) { changes, _ := common.GetChanges(commit, clone) sess.Out.Debug("[THREAD #%d][%s] %s changes in %d\n", tid, *repo.CloneURL, commit.Hash, len(changes)) - matchFile(sess, repo, commit, changes, tid) + findSecrets(sess, repo, commit, changes, tid) sess.Stats.IncrementCommits() sess.Out.Debug("[THREAD #%d][%s] Done analyzing changes in %s\n", tid, *repo.CloneURL, commit.Hash) From 39ac94cec42a84b8ed3426465bf37015f3d45eb0 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 15:26:53 -0400 Subject: [PATCH 110/226] factor out method for retrieving history --- core/analysis.go | 30 +++++++++++++++++++++--------- matching/matchfile.go | 4 ++-- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index 82c340c6..84991584 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -144,12 +144,12 @@ func matchContent(sess *Session, func findSecrets(sess *Session, repo *common.Repository, commit *object.Commit, changes object.Changes, threadId int) { for _, change := range changes { path := common.GetChangePath(change) - matchTarget := matching.NewMatchTarget(path, "") + matchTarget := matching.NewMatchTarget(path) if matchTarget.IsSkippable() { sess.Out.Debug("[THREAD #%d][%s] Skipping %s\n", threadId, *repo.CloneURL, matchTarget.Path) continue } - sess.Out.Debug("[THREAD #%d][%s] Matching: %s...\n", threadId, *repo.CloneURL, matchTarget.Path) + sess.Out.Debug("[THREAD #%d][%s] Matching file: %s...\n", threadId, *repo.CloneURL, matchTarget.Path) for _, fileSignature := range sess.Signatures.FileSignatures { matched, err := fileSignature.Match(matchTarget) @@ -199,12 +199,25 @@ func cloneRepository(sess *Session, repo *common.Repository, threadId int) (*git } sess.Stats.IncrementRepositories() sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) - return nil, "", nil + return nil, "", err } sess.Out.Debug("[THREAD #%d][%s] Cloned repository to: %s\n", threadId, *repo.CloneURL, path) return clone, path, err } +func getRepositoryHistory(sess *Session, clone *git.Repository, repo *common.Repository, path string, threadId int) ([]*object.Commit, error) { + history, err := common.GetRepositoryHistory(clone) + if err != nil { + sess.Out.Error("[THREAD #%d][%s] Error getting commit history: %s\n", threadId, *repo.CloneURL, err) + os.RemoveAll(path) + sess.Stats.IncrementRepositories() + sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) + return nil, err + } + sess.Out.Debug("[THREAD #%d][%s] Number of commits: %d\n", threadId, *repo.CloneURL, len(history)) + return history, err +} + func AnalyzeRepositories(sess *Session) { sess.Stats.Status = StatusAnalyzing var ch = make(chan *common.Repository, len(sess.Repositories)) @@ -234,16 +247,14 @@ func AnalyzeRepositories(sess *Session) { } clone, path, err := cloneRepository(sess, repo, tid) + if err != nil { + continue + } - history, err := common.GetRepositoryHistory(clone) + history, err := getRepositoryHistory(sess, clone, repo, path, tid) if err != nil { - sess.Out.Error("[THREAD #%d][%s] Error getting commit history: %s\n", tid, *repo.CloneURL, err) - os.RemoveAll(path) - sess.Stats.IncrementRepositories() - sess.Stats.UpdateProgress(sess.Stats.Repositories, len(sess.Repositories)) continue } - sess.Out.Debug("[THREAD #%d][%s] Number of commits: %d\n", tid, *repo.CloneURL, len(history)) for _, commit := range history { sess.Out.Debug("[THREAD #%d][%s] Analyzing commit: %s\n", tid, *repo.CloneURL, commit.Hash) @@ -255,6 +266,7 @@ func AnalyzeRepositories(sess *Session) { sess.Stats.IncrementCommits() sess.Out.Debug("[THREAD #%d][%s] Done analyzing changes in %s\n", tid, *repo.CloneURL, commit.Hash) } + sess.Out.Debug("[THREAD #%d][%s] Done analyzing commits\n", tid, *repo.CloneURL) os.RemoveAll(path) sess.Out.Debug("[THREAD #%d][%s] Deleted %s\n", tid, *repo.CloneURL, path) diff --git a/matching/matchfile.go b/matching/matchfile.go index 016cb839..5056f0fc 100644 --- a/matching/matchfile.go +++ b/matching/matchfile.go @@ -31,13 +31,13 @@ func (f *MatchTarget) IsSkippable() bool { return false } -func NewMatchTarget(path string, content string) MatchTarget { +func NewMatchTarget(path string) MatchTarget { _, filename := filepath.Split(path) extension := filepath.Ext(path) return MatchTarget{ Path: path, Filename: filename, Extension: extension, - Content: content, + Content: "", } } From f2affd5ad7df06fe65f56af01ed4b334e777a91b Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 15:37:56 -0400 Subject: [PATCH 111/226] hide some elements of the session subject on session save --- core/session.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/session.go b/core/session.go index a7e89113..f4962107 100644 --- a/core/session.go +++ b/core/session.go @@ -63,8 +63,8 @@ type Session struct { Targets []*common.Owner Repositories []*common.Repository Findings []*matching.Finding - IsGithubSession bool - Signatures matching.Signatures + IsGithubSession bool `json:"-"` //do not unmarshal to json on save + Signatures matching.Signatures `json:"-"` //do not unmarshal to json on save } func (s *Session) Initialize() { From 7ae2f181c3df2be26361a676f1902d6af388a771 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 15:46:43 -0400 Subject: [PATCH 112/226] error handling cleanup --- core/analysis.go | 6 +++--- core/session.go | 2 +- main.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index 84991584..8fae7542 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -124,14 +124,14 @@ func matchContent(sess *Session, content, err := common.GetChangeContent(change) if err != nil { - sess.Out.Fatal("Error retrieving content in commit %s, change %s.", commit.String(), change.String()) + sess.Out.Error("Error retrieving content in commit %s, change %s.", commit.String(), change.String()) } matchTarget.Content = content sess.Out.Debug("[THREAD #%d][%s] Matching content in %s...\n", threadId, *repo.CloneURL, commit.Hash) for _, contentSignature := range sess.Signatures.ContentSignatures { matched, err := contentSignature.Match(matchTarget) if err != nil { - sess.Out.Fatal("Error while performing content match: %s", err) + sess.Out.Error("Error while performing content match: %s\n", err) } if !matched { continue @@ -154,7 +154,7 @@ func findSecrets(sess *Session, repo *common.Repository, commit *object.Commit, for _, fileSignature := range sess.Signatures.FileSignatures { matched, err := fileSignature.Match(matchTarget) if err != nil { - sess.Out.Fatal(fmt.Sprintf("Error while performing file match: %s", err)) + sess.Out.Error(fmt.Sprintf("Error while performing file match: %s\n", err)) } if !matched { continue diff --git a/core/session.go b/core/session.go index f4962107..c7ce5782 100644 --- a/core/session.go +++ b/core/session.go @@ -169,7 +169,7 @@ func (s *Session) InitAccessToken() { func (s *Session) ValidateTokenConfig() { if *s.Options.Load == "" { if s.GitLab.AccessToken != "" && s.Github.AccessToken != "" { - s.Out.Fatal("Both a GitLab and Github token are present. Only one may be set.") + s.Out.Fatal("Both a GitLab and Github token are present. Only one may be set.\n") } if s.GitLab.AccessToken == "" && s.Github.AccessToken == "" { s.Out.Fatal("No valid API token was found.\n") diff --git a/main.go b/main.go index 30075f88..26aec044 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,7 @@ func main() { return "GitLab group" } }() - sess.Out.Fatal(fmt.Sprintf("Please provide at least one %s or user\n", host)) + sess.Out.Fatal("Please provide at least one %s or user\n", host) } core.GatherTargets(sess) From 2d38ccfa4e55ce311d744ce64300f945dc020258 Mon Sep 17 00:00:00 2001 From: "Greg Johnson (codeEmitter)" Date: Tue, 24 Mar 2020 17:32:59 -0400 Subject: [PATCH 113/226] indicate a content match in the output --- core/analysis.go | 29 +++++++++++++++++------------ core/bindata.go | 8 ++++---- core/session.go | 18 +++++++++++------- matching/findings.go | 30 ++++++++++++++++-------------- static/index.html | 2 +- 5 files changed, 49 insertions(+), 38 deletions(-) diff --git a/core/analysis.go b/core/analysis.go index 8fae7542..8265a98c 100644 --- a/core/analysis.go +++ b/core/analysis.go @@ -97,21 +97,26 @@ func createFinding(repo common.Repository, commit object.Commit, change *object.Change, fileSignature matching.FileSignature, + contentSignature matching.ContentSignature, isGitHubSession bool) *matching.Finding { + finding := &matching.Finding{ - FilePath: common.GetChangePath(change), - Action: common.GetChangeAction(change), - Description: fileSignature.GetDescription(), - Comment: fileSignature.GetComment(), - RepositoryOwner: *repo.Owner, - RepositoryName: *repo.Name, - CommitHash: commit.Hash.String(), - CommitMessage: strings.TrimSpace(commit.Message), - CommitAuthor: commit.Author.String(), - CloneUrl: *repo.CloneURL, + FilePath: common.GetChangePath(change), + Action: common.GetChangeAction(change), + FileSignatureDescription: fileSignature.GetDescription(), + FileSignatureComment: fileSignature.GetComment(), + ContentSignatureDescription: contentSignature.GetDescription(), + ContentSignatureComment: contentSignature.GetComment(), + RepositoryOwner: *repo.Owner, + RepositoryName: *repo.Name, + CommitHash: commit.Hash.String(), + CommitMessage: strings.TrimSpace(commit.Message), + CommitAuthor: commit.Author.String(), + CloneUrl: *repo.CloneURL, } finding.Initialize(isGitHubSession) return finding + } func matchContent(sess *Session, @@ -136,7 +141,7 @@ func matchContent(sess *Session, if !matched { continue } - finding := createFinding(repo, commit, change, fileSignature, sess.IsGithubSession) + finding := createFinding(repo, commit, change, fileSignature, contentSignature, sess.IsGithubSession) sess.AddFinding(finding) } } @@ -160,7 +165,7 @@ func findSecrets(sess *Session, repo *common.Repository, commit *object.Commit, continue } if *sess.Options.Mode == 1 { - finding := createFinding(*repo, *commit, change, fileSignature, sess.IsGithubSession) + finding := createFinding(*repo, *commit, change, fileSignature, matching.ContentSignature{}, sess.IsGithubSession) sess.AddFinding(finding) } if *sess.Options.Mode == 2 { diff --git a/core/bindata.go b/core/bindata.go index d72fa623..2bf300d8 100644 --- a/core/bindata.go +++ b/core/bindata.go @@ -261,7 +261,7 @@ func staticImagesSpinnerGif() (*asset, error) { return a, nil } -var _staticIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x5a\x7b\x6f\xdc\xb8\x11\xff\xff\x3e\x05\x8f\xc5\x1e\x12\xe0\xb4\x72\x6a\x1c\x50\x38\xd2\xa2\x69\x9c\x6b\x0c\x5c\x92\x43\xe2\x16\xe8\x5f\x06\x25\xce\x4a\x8c\x29\x52\x25\x67\xbd\x76\x8b\x7e\xf7\x82\x0f\x69\xf5\xda\x5c\xd6\x49\x80\x00\x81\x23\x52\xf3\xe2\x8f\xc3\xe1\xcc\x68\xb3\x1f\xb9\x2e\xf1\xa1\x05\x52\x63\x23\x37\x3f\x64\xee\x3f\x22\x99\xaa\x72\x0a\x8a\x6e\x7e\x20\x24\xab\x81\x71\xf7\x40\x48\xd6\x00\x32\x52\xd6\xcc\x58\xc0\x9c\xee\x70\x9b\xfc\x85\x0e\x5f\x29\xd6\x40\x4e\xef\x04\xec\x5b\x6d\x90\x92\x52\x2b\x04\x85\x39\xdd\x0b\x8e\x75\xce\xe1\x4e\x94\x90\xf8\xc1\xcf\x44\x28\x81\x82\xc9\xc4\x96\x4c\x42\xfe\xec\x67\x62\x6b\x23\xd4\x6d\x82\x3a\xd9\x0a\xcc\x95\x5e\x10\xcd\xc1\x96\x46\xb4\x28\xb4\x1a\x48\xff\xbb\x40\xa3\x8b\x0b\xf2\xfb\x0e\x51\xa8\x8a\x60\x0d\xe4\x5d\x0b\x8a\x7c\xd0\x3b\x53\x02\x11\x8a\xbc\xfb\x70\xf5\xf6\x7a\x41\x20\xdb\x61\xad\xcd\x40\xd6\x1b\x51\xd6\x0c\x24\x79\x0d\xca\x88\x5b\x0b\x8a\x3c\xf9\x6b\x23\xca\xba\x1b\x3e\xa5\x9b\x1f\x82\x14\x14\x28\x61\x13\x74\x67\x69\x18\xc5\x57\x52\xa8\x5b\x52\x1b\xd8\xe6\x34\xb5\xf8\x20\xc1\xd6\x00\x68\xd3\x42\x6b\xb4\x68\x58\xbb\x2e\xad\xa5\xc4\x80\xcc\xe9\xe1\x7d\x67\xde\x31\x6e\xdd\x82\x12\xa5\x56\xa2\x7c\x14\x7b\x2d\xaa\x5a\x8a\xaa\xc6\x47\x71\xb3\xb6\x95\xa2\x64\x0e\xf9\xe3\xfc\x59\x1a\x9c\xc5\x3d\x16\x9a\x3f\x74\x78\x28\x76\x47\x4a\xc9\xac\xcd\xa9\x62\x77\x05\x33\x24\xfc\x97\xc0\x7d\xcb\x14\x4f\x1a\xde\x4d\x78\x03\x49\x51\x85\x87\x68\x14\x21\x19\x17\xbd\x04\xb7\x55\x4c\x28\x30\xfd\x5b\x42\x32\x36\x96\x9f\x14\x86\x29\x4e\xbb\x85\x0c\x29\x45\x53\x11\x6b\xca\x9c\xa6\xa2\x61\x15\xd8\xb4\xd2\x6d\x0d\xe6\xc6\x59\xbe\x6e\x55\x45\x49\x70\x56\x7a\x7e\x46\x49\x0d\xce\x8c\x9c\xfe\xf9\x8c\x76\x0a\x78\x22\x94\x14\x0a\x92\x42\xea\xf2\x96\x12\x26\x31\xa7\x03\x05\x9d\x43\xb0\x81\xce\x62\x87\xa8\xd5\xc4\x44\xd4\x55\x25\xc1\x50\xe2\xce\x5f\x4e\x03\x0d\x25\x9c\x21\x8b\xef\xdc\x5a\xa5\x64\xad\x05\x4a\x98\x11\x2c\xc2\x05\x3c\xa7\x5b\x26\xfb\x59\xc9\x0a\xb7\x17\xd7\x9e\xc7\x01\x29\x2a\xbf\x4f\x03\xa3\x08\xc9\x6c\xcb\x8e\x58\x90\x38\xa7\xa2\x9b\x2c\x75\x24\x03\xab\xd3\x60\x52\xbf\x07\x29\x17\x77\xd1\x4b\x52\xc5\xee\xba\xcd\x6d\x98\x50\xc4\x68\x67\xae\x7b\xa4\xc7\xf7\x29\x2b\x0c\x49\x47\x5b\x2a\xb8\xf3\x21\x86\xf6\x66\x71\x57\x07\xbb\xde\x1a\x5d\x19\x70\x8e\xe7\x7d\x2e\xa7\x61\x6b\x2e\xc8\xf9\x59\x7b\xff\x7c\xbc\xd4\x05\xb6\xc4\x39\xdd\x70\x90\x58\x34\xa2\x05\x3e\x9e\x64\x4a\x34\x0c\x81\xd3\xb8\xa0\xee\x65\xc1\x0c\xf5\xc6\x76\x13\x37\x7e\x26\x9a\xe2\x1d\xe6\x82\x3c\x3b\x3b\x5b\x3d\x8f\x7b\x72\xc7\xe4\x0e\x94\xde\xe7\xf4\xd9\xd9\xd9\x70\xae\x11\x2a\xa7\xe3\x19\x76\x1f\xa8\x36\x57\x21\x22\x8a\xff\x08\x55\xad\xd7\xeb\x01\xe0\x13\xfc\x67\x60\x8e\x17\x6d\xf4\xfe\x28\x20\xa5\x96\x89\x6d\x46\xaf\x27\x04\xcc\x70\x82\x70\x8f\x49\x09\x0a\x21\xae\xdb\xcd\xde\x6c\x85\xe2\x42\x55\x76\xc2\x3d\xe7\x4f\xdc\xe1\x9f\x51\xb9\xbb\xe4\x7c\x44\xe6\x83\xe6\x82\x82\x1b\x0f\x0c\xdd\x9c\x65\x69\x7d\xbe\x20\xa6\x1d\x4b\x81\x7b\x5c\x12\xe2\x2e\x0b\xba\xf9\x35\x0e\xb3\xb4\x9d\x99\x3d\x46\x74\x71\x6a\x3e\xf1\xd5\xc0\x94\xf0\x2d\x91\x94\xf0\xa5\x30\x3a\x09\x1d\x86\x12\xbe\x3b\x00\x4b\xdd\x34\x02\xbf\x1d\x84\x51\xfe\x17\x81\xd8\xc9\x08\x30\xbe\x0c\xa3\xef\x0d\x48\x03\xad\xb6\x02\xb5\x11\xdf\xd0\x21\x87\x4a\xbe\x08\xd2\x91\xa0\x80\xeb\xfb\xc1\xd4\xf7\x06\x2e\x32\x53\xc1\x37\xf4\xd2\x28\xff\x8b\x20\xed\x64\x04\x34\xaf\xc3\xe8\x7b\x03\x92\xef\xcc\x3c\xab\xf9\x9a\x48\x76\x0a\x7a\x28\xcf\x2e\xfc\xbf\xc7\x20\xda\xcb\x0a\x90\x5e\xc6\xe1\xd7\xc1\x74\x34\x8c\x83\x71\x86\xd5\x8d\x2c\x94\x4e\x6d\xc8\x5c\x58\x05\x4b\x37\x78\x36\x5e\x5d\x77\x5d\x0e\xd5\x0b\xd5\xee\xb0\x5b\xee\x56\x9b\x26\x71\xd9\x9a\xd1\x92\x0c\x07\x89\x6d\xc8\x56\x6a\x86\x89\xf1\xb9\x7b\xcc\x6b\x03\x32\xad\x64\x25\xd4\x5a\x72\x30\x39\xfd\x00\xcc\x94\xf5\x7a\xbd\x0e\x88\xf5\x17\xb6\xf5\xf3\x43\xdb\x3c\xf4\x87\x21\xb2\x42\x42\x67\x48\x18\xf8\xbf\x4e\x75\x78\xa8\xf5\x1d\x98\x6e\x32\x64\x78\x41\x89\x9f\x5a\xce\x60\x32\x3c\x94\xb8\x87\x39\x33\xdb\x29\xac\x89\x2d\x75\x1b\xd2\x72\x3a\xf4\x69\x56\x06\xcf\x7c\x51\x86\x5d\xc6\xfa\x04\xe6\x96\x61\x4d\x37\xbf\x33\xac\x4f\x64\x0c\x97\x4b\x77\xad\x9c\xc8\xdc\x87\xd1\x87\x41\xfc\x7c\x98\x0b\xc9\xd2\x31\x12\x8e\x62\x82\x56\x86\xa1\xd6\x1b\x11\x8d\xa7\xb2\xd4\xe3\x7f\x70\xda\xe8\x99\x5d\x39\xe1\x0a\x87\x4d\xf6\x63\x92\x90\x74\xdd\x57\x02\x24\x49\xba\x1a\x63\xab\x35\x82\xf9\x64\x35\x38\x0c\x1b\xe1\xb9\xd9\xb9\x4c\x7e\x54\x24\x86\x7a\xb0\x46\x6c\xed\x45\x9a\x56\x02\xeb\x5d\xb1\x2e\x75\x93\x0e\x4b\x7c\x37\x6f\x74\x41\x49\x88\x8b\x39\xbd\x29\x24\x53\xb7\x74\x73\x28\xed\x88\xb0\x84\xb9\xd2\xe1\x23\x94\x48\x8a\x87\xb1\xec\x8b\x74\x24\xcf\x29\x98\x0b\x9b\x35\x1a\xbc\xdc\x9f\x1a\xc1\xb9\xc6\xe7\xa7\x1a\x9b\x0a\x6b\x77\x60\x53\x05\xfb\xb9\x2a\xb7\xbf\x06\x09\x53\xc4\x53\x0d\x6a\xd3\x51\x4d\xd7\x81\x1c\x86\xa1\xd1\x32\x38\xc4\x29\x42\xd3\x4a\x86\x31\x66\x76\xa3\xee\x4c\x1d\xaa\x3c\xe4\x4b\x67\xe3\xb0\x0d\x2b\x22\xb6\xe4\x49\x38\x2b\x24\xcf\x09\x7d\xa3\xb9\xd8\x3e\xd0\xa7\xe4\xbf\x64\x75\xb4\x66\x2d\x18\xaf\x80\xf8\xbf\x49\x6b\x44\xc3\x9c\xe7\xbe\x79\x77\x79\xf5\xeb\xbf\x66\x95\xeb\x8a\xfc\x8f\x80\xb4\x30\x55\x74\xa5\x2c\x18\x3c\x41\x91\xdd\x95\xa5\x2b\x3a\x37\x2f\xdf\xbf\x7a\x71\xfd\xea\xb3\x15\x5d\x82\x04\x84\x13\x14\x71\xa6\x2a\x57\xfb\x5e\xbe\xfa\xed\xd5\x11\x3d\xab\xc3\xa6\x21\x3f\x02\x76\x88\x25\x59\xa9\x39\x2c\xf8\xfd\x9f\xe8\x26\x5b\xe5\x04\x6b\x61\xd7\x2e\x72\x33\x44\xe0\x2e\xb7\x77\xc1\xe7\xc9\x53\xb2\xda\x8c\x5c\xc3\x4b\xf9\x84\xb2\x2e\xfe\x04\x75\xbd\x96\x6c\x95\x90\x10\x92\xfe\x61\x24\x59\x6d\x62\xab\x48\x69\xdd\x82\x3b\xa7\x4a\x1b\xd8\x82\xf1\x9d\x8f\x89\xa3\xf6\xd6\x35\x9a\x83\x5c\xdb\x5a\x1b\x0c\xa2\x5e\x33\x7b\xb0\xf0\x60\x5a\x7d\xc4\xb4\x61\x74\x1b\x19\x76\x08\x75\x8f\x30\x6e\xc8\xfe\x6e\xef\xc8\x57\x9b\x74\x3c\xfd\x96\x35\xd0\x5b\xd9\x99\x97\xa5\xe1\x30\x3d\xf6\x68\xdd\x34\x9a\x33\xb9\xd8\x0c\xf3\x6f\x12\x17\x91\xc7\x9d\x93\xfa\x97\x31\x45\x48\x76\xfc\x1a\x2e\x0f\x3d\x54\x6f\x69\xfd\xcb\xbc\x53\x35\x6e\x49\x75\xc0\x4a\x6d\x21\x36\xa8\xb8\xb0\x8d\xe8\xc5\x8f\x1b\x51\x2f\x3d\xdd\xdc\xed\x3d\x4d\x2d\x38\x07\x95\x53\x34\x2e\xc7\xfa\x09\x45\x03\xf6\xf9\x09\xad\xa7\xa5\xe5\x4f\x12\xbe\x18\x60\xbc\x23\x09\x7b\x0d\x16\xdf\x83\x83\x93\x3f\x79\x3a\x3f\x90\x03\x61\x4c\x82\x8b\x92\xee\x6f\xb2\x67\x46\xb9\xa0\x16\xfb\x40\x7e\x92\x6e\x32\x8b\x46\xab\x6a\xf3\x56\xa3\x28\xe1\x22\x4b\xe3\x98\x5c\xd7\xc2\x12\x57\x31\x13\xa9\xf5\xad\x25\xa8\x49\x01\x04\xc1\xfa\x7e\xb4\x09\xea\x67\x0d\x9d\xd1\xa9\x1e\xdb\x52\xa0\x4a\x2a\xa3\x77\x2d\xe9\x9f\xa6\xf9\xd5\x68\x19\x8b\xfb\x36\x48\xae\x6e\xee\x04\xec\x6f\x0c\xdb\xd3\x81\x06\x2f\xdb\x42\xa9\x15\xf7\xd1\xf4\x3d\xdb\x4f\x91\x3f\x41\x78\x0d\xf7\x7c\xd7\xb4\x9f\x52\xf0\x1a\xee\x89\xa3\x99\x6b\x99\x42\x33\xca\xf4\xa2\x9a\xa4\x01\x64\x89\x7f\x33\xc9\xdf\xcc\x34\x79\xab\x7d\x3e\x75\xb1\x90\xce\x20\xef\xe2\x55\xdc\xbb\xe5\x63\xdd\x6f\x6d\xba\x4c\xd7\x9f\xf3\x9e\x6c\x95\x90\x2e\x94\xfa\x17\xb3\xe8\x49\x96\xb2\xa9\x25\xd3\x5f\xf8\x6f\x12\xc7\x8c\xef\xa3\x6b\x20\xf3\xba\x1e\xa1\xe4\x0d\x58\xcb\x2a\x58\xd6\x72\xc8\xf5\x15\x26\x02\x99\x14\xe5\x20\x38\xa3\xd9\xa9\xd2\x39\x74\xb0\x23\x4a\x8a\xd1\xf9\x11\xa6\x5c\x5d\x1e\x59\xeb\x34\x9b\x0d\x90\xae\x12\x72\xc5\x0f\x10\x4f\x89\xa2\xb3\x0e\xdd\x53\xf0\x9b\x52\x8a\xb6\xd0\xcc\xf0\x99\x7b\xea\x1d\xfa\x76\x7e\xef\xa6\xc1\x69\x9b\x18\xe8\x7a\x46\x5f\xe2\x85\x4b\xc4\xab\x77\xd1\x60\x70\x99\x6b\x41\xb4\x38\x50\xf7\xed\xf4\xa5\x03\x35\xbe\xc2\xe7\x38\x4d\xb2\x65\x17\xce\x8f\xf6\x7a\x67\xc5\xb2\x0f\x89\xbe\x7b\x77\x63\x5b\xa1\x14\x98\xc5\xde\x7a\xfc\x12\x12\xa5\x44\x4a\x3a\xfe\x32\x12\x67\xd7\x95\xd8\xc6\xef\x1c\xbf\x69\xe6\x10\x0d\xa1\x2e\x7e\x33\xb3\x7d\x21\x37\x57\x4d\x87\x66\xbb\xa2\x79\x73\x4c\xc2\xa8\x34\x9e\x46\x83\xee\x53\xc1\x40\x41\xc7\x7a\x6c\x71\xad\x81\x63\x2c\x6e\x6f\x5a\x03\x7f\x44\xde\xc5\xb3\x29\xf5\x52\xf9\x3d\xdf\x97\x70\x33\x85\x94\xfa\xf8\x87\x98\x43\x9d\x42\x06\x67\x2d\x3c\xef\xfd\x07\x8e\xee\x43\xd8\x82\xb3\xf9\x37\xc5\x4e\x16\xbd\xb3\x91\x6b\xd1\x5e\x90\xbf\x19\xbd\xb7\x40\xba\x5a\xd7\x95\x27\x3b\xdb\x7d\x17\x5d\x90\xc3\x8c\xd1\xfb\x44\xc2\x16\x0f\x82\x98\xe2\xc7\x49\xe3\xfd\xd3\xd3\xba\x49\x72\x0b\x0f\x76\x3d\xbd\xc8\x7d\xf2\xe9\x70\x75\x37\x44\xe2\x60\xa5\x83\x64\xcc\xc5\xcb\x4f\xa6\x61\x4b\x79\xd8\xf4\xfc\x76\xb5\x40\xbc\xa9\xe3\xf5\xb4\xf9\xa7\x80\x7d\x70\x32\xad\xc2\x4a\x0e\x01\xac\x02\x7c\xad\x2d\xba\x18\x1e\xa3\x56\x3c\xaa\xec\x98\xe5\x31\xd5\x3d\x2d\xc3\xfd\x1c\xeb\x0f\x97\xe3\x92\xfd\x41\xed\x67\xad\x60\xb9\xa4\x1b\xe7\x9d\x53\xf7\x74\xd6\x15\x42\x71\xb8\xcf\x69\xf2\xac\xb3\x80\x0b\x26\x75\x35\xbe\xe0\xff\x28\x01\x0d\x3c\x24\x0c\x64\x9f\x36\x71\x5d\xee\x1a\x50\x78\xe4\x9b\x5e\x20\x8f\xc7\xd2\xf9\xd3\xf2\xc1\x1a\xb6\xb8\xba\xdc\x39\xc4\xa9\x8f\xec\x8e\x85\x09\x9b\x7e\xfc\xf7\x0e\xcc\x43\x72\xbe\x3e\x5f\x3f\x5b\x7f\xf4\x87\xbc\x5b\xfd\xa7\x19\x77\x8a\x83\xb1\xa5\x36\x70\x12\x5b\xc1\xca\xdb\x42\xab\xd3\x98\x5a\xdd\xb6\x60\x4e\xd3\xd3\xff\x66\xe0\x14\xae\xfe\x22\x3a\x89\x2b\x86\xbc\x93\x78\x86\x3f\x0c\x98\xf2\x65\x69\x68\x03\x65\x69\xf8\x79\xc9\xff\x03\x00\x00\xff\xff\x6a\x3a\x07\xd9\x6f\x22\x00\x00") +var _staticIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x5a\x7b\x6f\xdc\xb8\x11\xff\xff\x3e\xc5\x1c\x8b\x3d\x24\xc0\x69\xe5\xd4\xc0\xa1\x70\xa4\x45\xd3\x38\xd7\x18\xb8\x24\x87\xc4\x2d\xd0\xbf\x0c\x4a\x9c\x95\x18\x53\xa4\x4a\x72\xbd\x76\x8b\x7e\xf7\x82\x0f\x69\xf5\xda\x5c\xd6\x49\x80\x00\x81\x23\x92\xf3\xe2\x8f\xc3\xe1\x0c\xb9\xd9\x8f\x4c\x95\xf6\xa1\x45\xa8\x6d\x23\x36\x3f\x64\xee\x3f\x10\x54\x56\x39\x41\x49\x36\x3f\x00\x64\x35\x52\xe6\x3e\x00\xb2\x06\x2d\x85\xb2\xa6\xda\xa0\xcd\xc9\xce\x6e\x93\xbf\x90\xe1\x90\xa4\x0d\xe6\xe4\x8e\xe3\xbe\x55\xda\x12\x28\x95\xb4\x28\x6d\x4e\xf6\x9c\xd9\x3a\x67\x78\xc7\x4b\x4c\x7c\xe3\x67\xe0\x92\x5b\x4e\x45\x62\x4a\x2a\x30\x7f\xf6\x33\x98\x5a\x73\x79\x9b\x58\x95\x6c\xb9\xcd\xa5\x5a\x10\xcd\xd0\x94\x9a\xb7\x96\x2b\x39\x90\xfe\x77\x6e\xb5\x2a\x2e\xe0\xf7\x9d\xb5\x5c\x56\x60\x6b\x84\x77\x2d\x4a\xf8\xa0\x76\xba\x44\xe0\x12\xde\x7d\xb8\x7a\x7b\xbd\x20\x90\xee\x6c\xad\xf4\x40\xd6\x1b\x5e\xd6\x14\x05\xbc\x46\xa9\xf9\xad\x41\x09\x4f\xfe\xda\xf0\xb2\xee\x9a\x4f\xc9\xe6\x87\x20\xc5\x72\x2b\x70\x13\x74\x67\x69\x68\xc5\x21\xc1\xe5\x2d\xd4\x1a\xb7\x39\x49\x8d\x7d\x10\x68\x6a\x44\x6b\xd2\x42\x29\x6b\xac\xa6\xed\xba\x34\x86\x80\x46\x91\x93\xc3\x78\x67\xde\x31\x6e\xd5\xa2\xe4\xa5\x92\xbc\x7c\x14\x7b\xcd\xab\x5a\xf0\xaa\xb6\x8f\xe2\xa6\x6d\x2b\x78\x49\x1d\xf2\xc7\xf9\xb3\x34\x38\x8b\xfb\x2c\x14\x7b\xe8\xf0\x90\xf4\x0e\x4a\x41\x8d\xc9\x89\xa4\x77\x05\xd5\x10\xfe\x4b\xf0\xbe\xa5\x92\x25\x0d\xeb\x3a\xbc\x81\x50\x54\xe1\x23\x1a\x05\x90\x31\xde\x4b\x70\x4b\x45\xb9\x44\xdd\x8f\x02\x64\x74\x2c\x3f\x29\x34\x95\x8c\x74\x13\x19\x52\xf2\xa6\x02\xa3\xcb\x9c\xa4\xbc\xa1\x15\x9a\xb4\x52\x6d\x8d\xfa\xc6\x59\xbe\x6e\x65\x45\x20\x38\x2b\x39\x3f\x23\x50\xa3\x33\x23\x27\x7f\x3e\x23\x9d\x02\x96\x70\x29\xb8\xc4\xa4\x10\xaa\xbc\x25\x40\x85\xcd\xc9\x40\x41\xe7\x10\x74\xa0\xb3\xd8\x59\xab\xe4\xc4\x44\xab\xaa\x4a\xa0\x26\xe0\xf6\x5f\x4e\x02\x0d\x01\x46\x2d\x8d\x63\x6e\xae\x42\xd0\xd6\x20\x01\xaa\x39\x8d\x70\x21\xcb\xc9\x96\x8a\xbe\x57\xd0\xc2\xad\xc5\xb5\xe7\x71\x40\xf2\xca\xaf\xd3\xc0\x28\x80\xcc\xb4\xf4\x88\x05\x89\x73\x2a\xb2\xc9\x52\x47\x32\xb0\x3a\x0d\x26\xf5\x6b\x90\x32\x7e\x17\xbd\x24\x95\xf4\xae\x5b\xdc\x86\x72\x09\x5a\x39\x73\xdd\x27\x39\xbe\x4e\x59\xa1\x21\x1d\x2d\x29\x67\xce\x87\xa8\x35\x37\x8b\xab\x3a\x58\xf5\x56\xab\x4a\xa3\x73\x3c\xef\x73\x39\x09\x4b\x73\x01\xe7\x67\xed\xfd\xf3\xf1\x54\x17\xd8\x12\xe7\x74\xc3\x46\x62\xac\xe6\x2d\xb2\x71\x27\x95\xbc\xa1\x16\x19\x89\x13\xea\x06\x0b\xaa\x89\x37\xb6\xeb\xb8\xf1\x3d\xd1\x14\xef\x30\x17\xf0\xec\xec\x6c\xf5\x3c\xae\xc9\x1d\x15\x3b\x94\x6a\x9f\x93\x67\x67\x67\xc3\xbe\x86\xcb\x9c\x8c\x7b\xe8\x7d\xa0\xda\x5c\x85\x88\xc8\xff\xc3\x65\xb5\x5e\xaf\x07\x80\x4f\xf0\x9f\x81\x39\x9e\xb4\x56\xfb\xa3\x80\x94\x4a\x24\xa6\x19\x0d\x4f\x08\xa8\x66\x60\xf1\xde\x26\x25\x4a\x8b\x71\xde\xae\xf7\x66\xcb\x25\xe3\xb2\x32\x13\xee\x39\x7f\xe2\x36\xff\x8c\xca\x9d\x25\xe7\x23\x32\x1f\x34\x17\x14\xdc\x78\x60\xc8\xe6\x2c\x4b\xeb\xf3\x05\x31\xed\x58\x0a\xde\xdb\x25\x21\xee\xb0\x20\x9b\x5f\x63\x33\x4b\xdb\x99\xd9\x63\x44\x17\xbb\xe6\x1d\x5f\x0d\x4c\x81\xdf\x12\x49\x81\x5f\x0a\xa3\x93\xd0\x61\x28\xf0\xbb\x03\xb0\x54\x4d\xc3\xed\xb7\x83\x30\xca\xff\x22\x10\x3b\x19\x01\xc6\x97\xa1\xf5\xbd\x01\xa9\xb1\x55\x86\x5b\xa5\xf9\x37\x74\xc8\xa1\x92\x2f\x82\x74\x24\x28\xe0\xfa\x7e\xd0\xf5\xbd\x81\x6b\xa9\xae\xf0\x1b\x7a\x69\x94\xff\x45\x90\x76\x32\x02\x9a\xd7\xa1\xf5\xbd\x01\xc9\x76\x7a\x9e\xd5\x7c\x4d\x24\x3b\x05\x3d\x94\x67\x17\xfe\xdf\x63\x10\xed\x65\x05\x48\x2f\x63\xf3\xeb\x60\x3a\x6a\xc6\xc6\x38\xc3\xea\x5a\x06\x4b\xa7\x36\x64\x2e\xb4\xc2\xa5\x13\x3c\x1b\xcf\xae\x3b\x2e\x87\xea\xb9\x6c\x77\xb6\x9b\xee\x56\xe9\x26\x71\xd9\x9a\x56\x02\x86\x8d\xc4\x34\xb0\x15\x8a\xda\x44\xfb\xdc\x3d\xe6\xb5\x01\x99\x56\xd0\x12\x6b\x25\x18\xea\x9c\x7c\x40\xaa\xcb\x7a\xbd\x5e\x07\xc4\xfa\x03\xdb\xf8\xfe\xa1\x6d\x1e\xfa\x43\xd3\xd2\x42\x60\x67\x48\x68\xf8\xbf\x4e\x75\xf8\xa8\xd5\x1d\xea\xae\x33\x64\x78\x41\x89\xef\x5a\xce\x60\x32\x7b\x28\x71\x0f\x7d\x7a\xb6\x52\xb6\x06\x53\xaa\x36\xa4\xe5\x64\xe8\xd3\xb4\x0c\x9e\xf9\xa2\x0c\xab\x6c\xeb\x13\x98\x5b\x6a\x6b\xb2\xf9\x9d\xda\xfa\x44\xc6\x70\xb8\x74\xc7\xca\x89\xcc\x7d\x18\x7d\x18\xc4\xcf\x87\xb9\x90\x2c\x1d\x23\xe1\x28\x26\x68\x65\x36\xd4\x7a\x23\xa2\x71\x57\x96\x7a\xfc\x0f\x4e\x1b\x3d\xb3\x2b\x27\x5c\xe1\xb0\xc9\x7e\x4c\x12\x48\xd7\x7d\x25\x00\x49\xd2\xd5\x18\x5b\xa5\x2c\xea\x4f\x56\x83\xc3\xb0\x11\xbe\x9b\x9d\xcb\xe4\x47\x45\x62\xa8\x07\x6b\x6b\x5b\x73\x91\xa6\x15\xb7\xf5\xae\x58\x97\xaa\x49\x87\x25\xbe\xeb\xd7\xaa\x20\x10\xe2\x62\x4e\x6e\x0a\x41\xe5\x2d\xd9\x1c\x4a\x3b\xe0\x06\xa8\x2b\x1d\x3e\x62\x69\xa1\x78\x18\xcb\xbe\x48\x47\xf2\x9c\x82\xb9\xb0\xd9\x45\x83\x97\xfb\x53\xc3\x19\x53\xf6\xf9\xa9\xc6\xa6\xdc\x98\x1d\x9a\x54\xe2\x7e\xae\xca\xad\xaf\xb6\x40\x25\x78\xaa\x41\x6d\x3a\xaa\xe9\x3a\x90\x43\x33\x5c\xb4\x0c\x36\x71\x6a\xb1\x69\x05\xb5\x31\x66\x76\xad\x6e\x4f\x1d\xaa\x3c\xcb\x96\xf6\xc6\x61\x19\x56\xc0\xb7\xf0\x24\xec\x15\xc8\x73\x20\x6f\x14\xe3\xdb\x07\xf2\x14\xfe\x0b\xab\xa3\x35\x6b\x41\x59\x85\xe0\xff\x26\xad\xe6\x0d\x75\x9e\xfb\xe6\xdd\xe5\xd5\xaf\xff\x9a\x55\xae\x2b\xf8\x1f\xa0\x30\x38\x55\x74\x25\x0d\x6a\x7b\x82\x22\xb3\x2b\x4b\x57\x74\x6e\x5e\xbe\x7f\xf5\xe2\xfa\xd5\x67\x2b\xba\x44\x81\x16\x4f\x50\xc4\xa8\xac\x5c\xed\x7b\xf9\xea\xb7\x57\x47\xf4\xac\x0e\x8b\x66\xd9\x11\xb0\x43\x2c\xc9\x4a\xc5\x70\xc1\xef\xff\x44\x36\xd9\x2a\x07\x5b\x73\xb3\x76\x91\x9b\x5a\x8b\xcc\xe5\xf6\x2e\xf8\x3c\x79\x0a\xab\xcd\xc8\x35\xbc\x94\x4f\x28\xeb\xe2\x4f\x50\xd7\x6b\xc9\x56\x09\x84\x90\xf4\x0f\x2d\x60\xb5\x89\x57\x45\x52\xa9\x16\xdd\x3e\x95\x4a\xe3\x16\xb5\xbf\xf9\x98\x38\x6a\x6f\x5d\xa3\x18\x8a\xb5\xa9\x95\xb6\x41\xd4\x6b\x6a\x0e\x16\x1e\x4c\xab\x8f\x98\x36\x8c\x6e\x23\xc3\x0e\xa1\xee\x11\xc6\x0d\xd9\xdf\xed\x1d\xf9\x6a\x93\x8e\xbb\xdf\xd2\x06\x7b\x2b\x3b\xf3\xb2\x34\x6c\xa6\xc7\x6e\xad\x9b\x46\x31\x2a\x16\x2f\xc3\xfc\x48\xe2\x22\xf2\xf8\xe6\xa4\xfe\x65\x4c\x11\x92\x1d\x5f\xc8\xc1\x07\x5e\x49\x6a\x77\x1a\xe1\x0d\xb5\x65\x7d\x01\x6e\x0a\x6e\xa4\x1f\xb8\x3c\x5c\xb3\xc2\x6a\xe3\xb2\x8a\x74\x03\x2f\xc3\x15\xe9\x32\x7b\x1c\x3c\x22\x21\x4b\xeb\x5f\xe6\xd7\x61\xe3\x7b\xaf\x6e\xf5\x84\x32\x18\x6f\xc1\x18\x37\x0d\xef\xe7\x30\xbe\xed\x7a\xe9\xe9\xe6\x7b\xcb\xd3\xd4\x9c\x31\x94\x39\xb1\xda\x25\x72\x3f\x59\xde\xa0\x79\x7e\xc2\xfd\xd6\x12\xc6\x93\xac\x32\x46\x31\xef\xad\xdc\x5c\xa3\xb1\xef\xd1\xad\x19\x7b\xf2\x74\xbe\xeb\x07\xc2\xa8\x40\x17\x8a\xdd\xdf\x64\x4f\xb5\x74\x91\x33\x5e\x36\xf9\x4e\xb2\xc9\x8c\xd5\x4a\x56\x9b\xb7\xca\xf2\x12\x2f\xb2\x34\xb6\xe1\xba\xe6\x06\x5c\x59\x0e\x42\xa9\x5b\x03\x56\x41\x81\x60\xd1\xf8\x4b\x6f\x1d\xd4\xcf\x6e\x8d\x46\xa1\x63\x6c\x4b\x61\x65\x52\x69\xb5\x6b\xa1\xff\x9a\x26\x71\xa3\x69\x2c\xae\xdb\x20\x83\xbb\xb9\xe3\xb8\xbf\xd1\x74\x4f\x06\x1a\xbc\x6c\x83\xa5\x92\xcc\x87\xec\xf7\x74\x3f\x45\xfe\x04\xe1\x35\xde\xb3\x5d\xd3\x7e\x4a\xc1\x6b\xbc\x07\x47\x33\xd7\x32\x85\x66\x94\x4e\x46\x35\x49\x83\x96\x26\x7e\x64\x92\x24\xea\x69\x86\x58\xfb\xa4\xed\x62\x21\x67\xb2\xac\x0b\x8a\x71\xed\x96\x63\x47\xbf\xb4\xe9\x32\x5d\x1f\x4c\x7a\xb2\xb8\x51\x9d\x5e\x3f\x30\x0b\xd1\xb0\x94\xb2\x2d\x99\xfe\xc2\x3f\x7c\x1c\x33\xbe\x0f\xe1\x81\xcc\xeb\x7a\x84\x92\x37\x68\x0c\xad\x70\x59\xcb\xa1\xa0\x90\x36\xe1\x96\x0a\x5e\x0e\x4e\x00\xab\x77\xb2\x74\x0e\x1d\xec\x88\x92\xe2\x11\xf0\x08\x53\xae\x2e\x8f\xcc\x75\x9a\x32\x07\x48\x57\x09\x5c\xb1\x03\xc4\x53\xa2\xe8\xac\x43\xf7\xe4\xec\xa6\x14\xbc\x2d\x14\xd5\x6c\xe6\x9e\x6a\x67\xfd\x9b\x41\xef\xa6\xc1\x69\x9b\x18\xe8\x7a\x46\x5f\x47\x86\x93\xca\xab\x77\xd1\x60\x90\x31\x28\x0e\x8a\x1f\xa8\xfb\x3b\xfb\xa5\x0d\x35\xce\x13\xe6\x38\x4d\x52\x72\x77\x66\x1c\xbd\x50\x9e\x55\xe4\x3e\x24\xfa\x2b\xc2\x1b\xd3\x72\x29\x51\x2f\x5e\xe0\xc7\xe7\x96\x28\x25\x52\x92\xf1\xf3\x4b\xec\x5d\x57\x7c\x1b\x1f\x53\x7e\x53\xd4\x21\x1a\x42\x5d\x7c\x98\x33\x7d\xb5\x38\x57\x4d\x86\x66\xbb\xca\x7c\x73\x4c\xc2\xa8\xfe\x9e\x46\x83\xee\x3d\x62\xa0\xa0\x63\x3d\x36\xb9\x56\xe3\x31\x16\xb7\x36\xad\xc6\x3f\x22\xef\xe2\xd9\x94\x7a\xa9\xc6\x9f\xaf\x4b\x38\x99\x42\xde\x7e\xfc\xb5\xe7\x50\x0c\xc1\x60\xaf\x85\xef\xbd\x7f\x45\xe9\x5e\xdb\x16\x9c\xcd\x8f\x14\x3b\x51\xf4\xce\x06\xd7\xbc\xbd\x80\xbf\x69\xb5\x37\x08\x5d\x41\xed\x6a\xa0\x9d\xe9\x1e\x5f\x17\xe4\x50\xad\xd5\x3e\x11\xb8\xb5\x07\x41\x54\xb2\xe3\xa4\xf1\xfc\xe9\x69\x5d\x27\xdc\xe2\x83\x59\x4f\x0f\x72\x9f\xe1\x3a\x5c\xdd\x09\x91\x38\x58\xc9\x20\xe3\x73\xf1\xf2\x93\xb9\xde\x52\xb2\x37\xdd\xbf\x5d\xc1\x11\x4f\xea\x78\x3c\x6d\xfe\xc9\x71\x1f\x9c\x4c\xc9\x30\x93\x43\x00\xab\xd0\xbe\x56\xc6\xba\x18\x1e\xa3\x56\xdc\xaa\xf4\x98\xe5\x31\x9f\x3e\x2d\x8d\xfe\x1c\xeb\x0f\x87\xe3\x92\xfd\x41\xed\x67\xcd\x60\xb9\x6e\x1c\x27\xb7\x53\xf7\x74\xd6\x15\x5c\x32\xbc\xcf\x49\xf2\xac\xb3\x80\x71\x2a\x54\x35\x3e\xe0\xff\x28\xcb\x0d\x3c\x10\x1a\xa2\x4f\x9b\x98\x2a\x77\x0d\x4a\x7b\xe4\xe1\x30\x90\xc7\x6d\xe9\xfc\x69\x79\x63\x0d\xef\xd1\xba\x04\x3d\xc4\xa9\x8f\xf4\x8e\x86\x0e\x93\x7e\xfc\xf7\x0e\xf5\x43\x72\xbe\x3e\x5f\x3f\x5b\x7f\xf4\x9b\xbc\x9b\xfd\xa7\x19\x77\x92\xa1\x36\xa5\xd2\x78\x12\x5b\x41\xcb\xdb\x42\xc9\xd3\x98\x5a\xd5\xb6\xa8\x4f\xd3\xd3\xff\x30\xe1\x14\xae\xfe\x20\x3a\x89\x2b\x86\xbc\x93\x78\x86\xbf\x3e\x98\xf2\x65\x69\xb8\x6b\xca\xd2\xf0\x1b\x96\xff\x07\x00\x00\xff\xff\x87\x1d\x5d\xd8\xd4\x22\x00\x00") func staticIndexHtmlBytes() ([]byte, error) { return bindataRead( @@ -276,12 +276,12 @@ func staticIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/index.html", size: 8815, mode: os.FileMode(420), modTime: time.Unix(1583790160, 0)} + info := bindataFileInfo{name: "static/index.html", size: 8916, mode: os.FileMode(420), modTime: time.Unix(1585084588, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _staticJavascriptsApplicationJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x5b\xef\x72\xdb\x38\x92\xff\x9e\xa7\xc0\x22\x9e\x33\x19\x53\x94\xec\xbb\xcc\xce\xd0\x51\x72\x9e\xfc\xf5\xd5\x4c\x26\x95\x64\xee\xaa\xce\xf2\xfa\x20\xb2\x25\x21\xa6\x40\x15\x00\x59\x72\x62\x5d\xed\xd3\xec\x83\xcd\x93\x5c\x01\x04\x48\x80\xa4\x64\x39\xf7\x61\xa7\xb6\x1c\x11\xe8\xfe\x75\xa3\x01\xf4\x3f\x72\x6f\x08\x47\x9f\x24\x91\x02\x0d\xd1\x2f\x24\xbd\x1e\x17\x0c\xe2\xdf\x8a\x0c\xf2\x18\xd6\x12\x58\x16\x7c\x7b\x84\xd0\x92\xe7\x09\xc2\x7d\xa1\x08\x71\xf4\x08\xa1\x0c\x26\x64\x99\x4b\x91\x20\x35\x8d\x10\x56\x18\x4b\x81\x13\x64\xfe\xc3\x94\x51\x49\x49\x4e\xbf\x52\x36\xd5\x2c\x25\x11\x97\x90\x9d\x49\x43\xc7\x96\x79\x6e\xa6\xde\x50\x46\xc5\xac\x9e\x73\xa6\x3e\xf0\x62\xca\x41\x54\xe0\x03\x33\xfe\x99\xf0\x29\xc8\x5a\xa6\x1d\xff\x08\x8b\x42\x50\x59\x70\x0a\x7a\xd2\x8e\xbf\x2c\xe6\x73\xda\x41\xff\x86\xe6\xe0\x68\xee\x8c\xb3\x8c\xb2\xa9\x2f\x77\xa3\xfe\x50\x61\xd5\x4d\xd0\x64\xc9\x52\x49\x0b\x16\x84\xc6\x14\x1c\xe4\x92\x33\x24\x67\x54\xc4\x53\x90\x81\x35\x4d\x88\x86\xc3\x21\xc2\x13\xc3\x89\x4f\x2d\x5a\xb6\xe4\x44\x21\x74\x60\xd1\x09\x0a\x3c\x20\x63\xbe\x12\x4b\xd9\xc8\x52\x56\x72\xf1\x60\x90\xe8\xff\x69\x01\x08\x6d\xf4\x5f\xb5\xcd\xc0\xb2\xd3\xea\x41\x28\x2c\x34\x44\xaf\x88\x84\x78\x41\xb8\x80\x6e\x41\xe1\xa9\xaf\x48\xbd\xf4\x20\xac\x65\x03\xcb\xb6\x61\x39\x1b\x6b\xc1\x36\x08\x72\x01\x5d\xcc\xac\x58\x05\x61\x53\xef\x39\xcd\x73\x2a\x10\x42\x43\x4d\xda\x2b\x75\x77\x96\x02\x69\xc1\x32\xa1\xe6\x7f\x23\x72\x16\x4f\xf2\xa2\xe0\x81\xe1\xea\xa3\xe3\xc1\x60\x10\xd6\xd4\xca\x68\x4a\x16\x1a\x22\x06\x2b\x2d\x36\xd0\x86\x2c\x49\xec\x74\x2c\x40\x7e\x2a\x81\x03\x23\xc0\x50\x18\x3b\x57\x84\xb2\x38\xff\xf4\xfb\x27\xc9\x29\x9b\x06\x61\x2c\x96\x63\x21\x79\x70\x7c\x1c\xa1\x9f\x42\xb3\xc5\x9b\xf0\xf4\xd1\x8a\xb2\xac\x58\xc5\xc2\x5c\x35\x25\x5a\x5f\xbb\xd3\x47\x8f\x94\x56\xe6\xac\xed\xbc\x84\x34\x3b\x93\x92\xd3\xf1\x52\x42\x82\xf0\x79\xa6\x6f\x95\x04\x21\xd5\x01\x3e\x67\x19\x4d\x89\x2c\xb8\x48\xd0\x05\x56\xa3\x38\x42\xf8\x4a\x2c\x20\x55\x3f\x26\x74\x2d\x97\x1c\xd4\xcf\x79\x91\x5e\xab\x7f\x85\x5c\x8e\xf5\x14\xb9\xd6\xe3\x19\xcc\x0b\x3d\x4e\xe6\x8b\x1c\xf0\xa5\x42\x17\xb3\x82\xcb\xf2\xde\xbc\x23\x62\xb6\xcf\x69\xaf\xa9\x71\x65\x8d\x41\x84\xfe\x1a\x56\xe7\x5d\x72\x3a\x9f\x43\x56\x12\xfe\x06\x42\x90\x29\x74\x20\xeb\xad\x2f\x67\xd1\xb0\x25\xc0\xf0\x29\x19\x8b\x9c\xca\x00\xf7\xd4\x7f\xaf\xdf\xbf\x42\x1f\xde\x7e\x40\x9f\xce\xdf\xbe\x3f\xfb\xfc\xc7\xc7\xd7\x7a\x14\x47\xe8\x24\x8c\x17\xc5\x22\xf0\xb7\xd0\xa0\xc7\x1c\x16\x39\x49\x21\xe8\xff\x6d\x24\x46\xe2\x49\x3f\x42\x18\x87\xf5\xa8\x1e\x3c\x28\x47\x6b\x0f\xf0\x19\x84\xfc\x08\x39\x91\x9d\x4e\x40\x29\xbf\x20\x72\xe6\x69\xae\xf6\xe9\x03\x91\xca\x30\xb2\xf8\xb5\x58\x01\x7f\x49\x04\x58\xa5\x26\x05\x47\x81\xe2\xa3\x68\x88\x06\xa7\x88\xa2\x67\x25\x6f\x7b\x8b\xe3\x1c\xd8\x54\xce\x4e\x11\x3d\x3a\xaa\x2f\xa1\xba\xa3\x4a\x66\x4c\x59\x06\xeb\xdf\x27\xc1\x16\xee\x0b\x7a\x19\xa2\xe7\xa8\x77\x5c\xb3\xd6\xfb\xc8\x97\x70\x6a\x06\x37\xce\x3d\x34\xd3\x13\x92\x0b\xa8\x36\x72\x42\x73\x78\x59\x30\x09\x4c\x8a\x3f\x54\x84\xd8\x76\x3a\x2e\x70\x7f\xa2\x9d\x6c\xe4\x58\xa3\x72\xd3\xb7\xbf\xaf\x18\x70\x1c\x76\x4f\xbe\x27\x73\xf0\xe7\xdc\x13\x16\x75\x9a\xf7\x32\xfe\x52\x50\x16\xe0\x3e\x0e\x3b\x95\x75\x34\x4d\x49\x9e\x8f\x49\x7a\x1d\x21\xe0\xbc\xe0\x56\xf1\x83\x98\x7c\x21\xeb\xc0\xda\x47\xc7\x3f\x2d\xa9\xb1\xe6\x20\x8c\x0c\x89\x58\xa6\x29\x08\x91\xa0\x0a\xd1\xba\x37\x85\x9b\x94\xff\x94\x16\x75\xfd\x82\x7b\xfb\xbd\x18\xfc\xb2\xc8\x73\xd0\x3a\x76\x04\xe2\x89\x0d\x4d\x4a\xc8\x5c\x39\x8a\xc4\x82\x18\x58\xe3\x6f\x26\x35\xb2\x72\x39\x56\x50\x60\x25\x6b\x1f\xf4\x9f\x14\x56\xae\x68\xf5\xec\x3b\x9e\x44\xb9\x0b\x22\xc5\x55\x5a\x30\x49\xa8\xda\x2e\x47\xb2\x9e\x52\xcf\x8b\x22\xcf\x29\x9b\x7e\xa6\xe9\x35\xf0\xa4\x8a\xe1\x36\xc0\x35\xc7\x0d\xf9\x39\x93\xc0\x6f\x48\x9e\xa0\xa7\x03\x1d\x63\xab\xd4\xa1\xcb\x2d\xe8\x5d\xc8\xa9\x90\xc0\x3e\x17\xe5\x11\xd7\x6a\x44\x08\xa7\x33\xc2\xa6\x60\x0f\x19\x07\x96\x01\x0f\x6b\x26\x1d\x36\x5e\x79\xba\xd8\xbb\x57\xcf\x7f\x28\x75\x0a\xea\x83\x53\xe2\xec\x0a\xce\x5a\xfe\x96\xc8\x68\x90\x8b\x85\x07\xec\xcd\x74\xab\xb4\xe9\x92\x31\x23\xe2\xa5\x5e\x64\x16\xd4\x69\x51\x53\xda\x72\x91\x11\x09\x76\x7a\x6f\xbc\x2a\xdd\xe9\xc6\x73\x8f\xce\x9e\x78\xea\xc6\x6f\x03\xcb\x61\x7f\x24\x9b\xb8\x75\x63\x99\xd9\xbd\xd1\xbc\xf4\xb0\x1b\xd2\x25\xd9\x1b\xd7\xa6\xa3\xdd\x90\x66\xd6\x45\xd3\xa7\xcb\x3d\x74\xdb\x4e\xbb\x77\xad\xd0\x10\x09\x90\xf6\xce\x04\x2d\x0e\x54\x5e\x47\x7d\x87\x4b\x25\x27\x20\xd3\x59\x25\x38\xf2\x30\x2d\x4e\x7d\xdc\x9d\xb3\xba\xeb\xcc\xfb\x3a\xfd\xa5\x95\x8d\xa6\x39\x10\x5e\x69\xd9\x66\xe9\xb4\xc3\xab\x86\xa3\xe8\x36\x87\x4f\xf5\x10\x7b\x94\x5b\x61\xf9\x83\xd0\x5a\xa4\x4a\x11\x2b\x0b\xec\xa7\x49\x13\xaf\x91\x2b\xfb\x7e\x6f\x3f\x23\xf9\x3c\x4d\x2b\xf9\x02\x3b\xd4\x3a\x08\xf0\xe3\x94\xf0\xec\xca\xe2\x5c\xdd\x90\x7c\xa9\xd2\x24\x09\x6b\xe9\x1e\xdd\xac\xd2\xba\x5e\xb9\xef\x39\xb6\xa4\x35\x42\x97\x33\x36\xb1\x29\x9f\x3e\x17\xef\x96\x73\x52\x59\xe0\x20\xc0\x92\xca\xbc\x12\x8b\xdf\x52\xc9\x8b\x71\x82\x30\x3a\x32\xfc\x35\xe5\xe3\x85\x91\x77\x35\x26\xdc\x72\x18\xa2\x38\x15\x22\xc0\x2b\x9a\xc9\x99\x75\xeb\xa5\xf6\x3a\xe0\xd7\x1e\x10\x1d\x21\xfc\x03\x6e\xda\x7f\x97\x5f\xee\x10\xcc\x61\x5e\xdc\xc0\xcb\x9c\x28\x99\x76\xae\x37\x26\xbc\x47\x18\x9d\xab\x44\x0f\x79\xa3\x42\x72\xba\x80\x0c\x37\xb4\xc4\xc7\x83\x41\xa5\x4b\x63\xe7\xac\x13\xdd\xb5\x73\x36\x64\x57\x3b\x37\xa3\x19\x04\xed\x0d\xb4\xd5\x95\x71\xda\x3a\xa5\x4c\x49\x0e\xb6\x14\x09\xe3\x09\xc9\xe0\x9c\x05\x78\x42\x84\xc4\xcd\x5d\xd6\x2e\x78\xb7\x1e\x39\xec\xab\x84\xf6\xf4\x0f\xd5\xc0\x38\xee\x5d\x3a\xa4\x25\xc9\x5e\x5a\x54\x51\xe2\xa1\x7a\xb8\xde\x7e\x97\x32\xdc\xa1\xdb\x4b\x23\x3f\xd2\x3c\x54\x2d\x13\x31\x76\x69\x24\x4b\x92\xbd\x94\xa9\xc2\xd3\xfe\x7a\x78\x77\x7b\xa7\x37\x28\x0f\xbb\x58\x51\x15\x69\x9a\x92\x6d\xff\xc3\xb2\xa5\x44\x40\xa3\x3f\x94\x38\xae\x5a\xfb\x16\x7c\xee\x4e\xdb\x94\x69\xcc\x81\x5c\x9f\x3a\x20\x53\x22\x67\xc0\xbb\x11\xde\xda\x39\xe4\x6e\xdc\x76\x2c\xc2\x48\x7e\xbb\x45\x9b\x33\x3b\xe7\x63\x6d\x83\xaa\x7a\x3c\x6d\xa4\x37\x6e\xfb\xa7\xc1\x6c\x9a\x6a\x6d\xa6\x3f\xd8\x35\x2b\x56\xac\x8b\xc7\x2b\xcf\x0c\xc7\x11\xc2\x28\x50\xae\x56\xf7\x62\xce\x59\xfb\x30\xb8\xb9\xa3\x72\x9d\x61\xd9\x8d\x6a\x75\x2a\x4c\x65\x50\x75\x2b\xd4\x73\xf0\x4d\xe5\xfc\xea\x0c\x36\x4b\x82\xb0\x59\xd0\xdc\x57\x58\x48\x32\x55\xe5\x5d\x82\xb0\x2c\x0b\x0a\xb8\x29\xcb\x33\xd3\x54\x4c\x73\x9a\x5e\x23\x99\xc5\x69\x91\xf7\x74\x39\x4d\xb0\x2a\x45\x66\xc5\xca\x48\xc0\x55\x4f\x4e\xc2\x7c\xa1\xaa\xf1\x04\x5d\xc5\xf6\x77\xa0\xb4\xb4\x0f\xd6\xb1\xaa\x7b\x22\xe7\x79\x10\x86\x3b\xb3\x7b\x6d\xb2\x03\x95\xe3\x29\x62\x53\x4a\x1b\x58\xc7\x9c\xc4\xb6\x64\x44\x18\xc6\x19\x91\x24\xc0\x56\x8e\x1b\xb0\xb6\x85\x26\xa7\x8b\xd0\xaa\x1a\x94\x70\x92\x65\x26\x20\xa9\x3a\xbe\xc7\x4b\x52\x1c\x76\x6c\xbe\xe2\xa9\xab\xdd\x82\xcf\x89\x94\x90\xd9\xa2\x78\xdb\xf5\x5d\xe4\x54\x0a\xa4\x9b\x6b\x5d\x6e\xdd\xb4\x2b\x4c\x8f\xa5\x8f\x9d\x46\x9a\x8a\x0f\x8c\xcc\x41\xb1\x96\x30\x6e\x8b\x45\x51\x64\x94\x43\xaa\x6a\x78\x0b\x0e\x79\x4e\x17\x82\x0a\xfa\x15\x02\xc3\x52\x15\xea\x11\xfa\x71\x10\xa1\x93\xa7\x8e\xa5\x1c\xfe\xe1\x10\x61\xdc\xee\x75\x3e\x13\x92\x17\x6c\xfa\x5c\x1d\xf6\xab\x18\x44\x4a\x16\x10\x58\xc5\xf4\xd1\x7e\xd6\xb7\x24\x1d\x26\xab\x58\x2a\x49\x9a\xa7\x8f\x35\xe7\x03\xb1\xb5\xdd\x9d\x15\x3a\x16\x17\x92\x47\x68\x4e\xd9\xaf\xba\x6d\x13\x21\xc8\xa6\x50\xfe\xb6\x4b\x12\x52\x25\xb2\xc6\x23\x0b\xc9\x1d\x2b\x08\xc9\x4d\xbf\x07\x3d\xab\x41\xd0\xdd\x1d\x72\x67\x86\x28\xa8\x51\xd1\x13\x74\x12\xb6\xac\x25\x24\x6f\xb5\x84\xb3\x29\x94\x34\x43\x74\xc6\x39\xb9\x75\x41\x8e\xd0\x71\x68\xf6\x27\x76\x37\x7e\x4e\x33\x43\x31\x74\x55\xe8\x21\x5f\x81\x53\xb7\x11\x26\x81\x33\x2d\x05\x6b\xc7\xa4\xe5\x1e\x21\x1c\xc6\xdf\xd4\x63\x8d\x78\x84\xf0\xc6\xa7\xc0\xa7\xbe\x87\xe3\x55\x63\x4e\x79\xa5\x8f\x30\x7d\xbd\x5e\x04\x46\x42\x18\x21\x7c\x70\xfc\xe7\xdf\xff\x71\x70\xe2\x86\xb1\xda\x5d\x38\x7b\x02\xd6\x3e\x10\x2f\xb8\xf6\x3b\xaf\x4a\xf7\xeb\xf5\x04\xe6\x84\x5f\x9f\x89\x4f\x90\x43\xaa\xaf\xa8\x63\x85\x22\x23\xb9\xe3\x1f\x8d\x84\xdf\xd4\x70\xd5\x37\x32\x0d\x12\xa7\x4b\x61\x9b\x42\x79\x82\xf0\x63\xe3\x29\xae\x34\x16\x8a\xf5\x3f\xbd\xb4\xec\x2e\x61\xa7\x57\x84\x6a\x69\xa6\xad\xe1\x64\xda\x3e\x0a\x0e\x4b\x98\xa0\xc5\xa8\xcb\xc0\x37\x4e\xfb\xca\xe9\x71\xf8\xcb\xdc\xe5\x0d\xd3\xbc\x10\x20\x64\x80\xe5\xb8\xc8\x6e\x71\xa8\x3b\x4c\x01\x96\x3c\x96\x64\x9c\x43\x4f\x18\x8c\x66\x3e\xdd\x9c\x3d\x7d\xb4\xcd\xcf\x75\x10\x76\xf5\xca\xee\x8b\x2d\x69\xd5\x3f\x4b\x90\x4d\xa9\xbf\xa7\xb9\x54\xe3\x44\x08\x93\x2c\xf3\xdb\x4b\x46\x1b\x77\x39\x15\x7b\xd9\x16\xb3\x6d\xa9\xa4\x4a\xd5\x23\x74\x15\x67\x30\x2e\x96\x2c\x35\xa1\xa4\x4c\xf8\x22\xf4\x74\x30\x08\xdb\x1b\x2b\xae\x04\x10\x9e\x2a\x3f\x5c\xb0\x00\x5f\xc3\xed\x72\xd1\x01\x52\x12\x59\x29\x11\x3a\xe9\x04\xab\x4e\x89\x82\x52\x37\x23\x1e\x8b\xf2\xc4\xe0\xc8\xb9\x1c\xea\x3e\xb8\xc5\x52\x56\xa4\xcb\xb9\x1a\xb3\x2a\x64\x2a\x1f\x89\x3a\xae\x93\x93\x08\x42\x7c\x0d\xb7\x2f\x8b\xcc\x9b\xd3\x19\xd2\xbf\xfe\x35\xa9\x06\x6c\x34\xb1\x2f\x40\x26\xce\x06\xeb\xab\x49\x8b\xa5\x30\xcb\xaa\xbb\x67\x8d\x34\xa8\x46\xfe\x79\x4f\x64\x06\x6b\xb9\x0f\x6a\x23\x29\xab\x7d\x51\x4d\xb2\xa9\x7e\x29\x7f\x6d\xa4\x58\xb7\xa8\x42\xd7\xc0\x35\xc0\x2e\x7e\x4f\x43\x92\x4a\x7a\x03\x95\x8e\xfb\xdc\x27\x07\xe3\x9e\x2b\x55\xdb\x67\x2f\x47\xe6\x38\x33\x8b\xef\x27\x3b\x55\x1f\xfc\x21\xde\xcd\xf5\x70\xbb\xbc\xdc\xb6\x33\xec\x79\x3a\xb4\x8f\xb7\x73\x25\x6e\xca\xfe\x8f\x3e\xd1\x33\x9a\x65\xc0\x1e\x7a\x17\x96\x6c\xac\xbd\x9f\xbd\x0f\x15\x70\xa3\x94\xdb\xe6\x69\x6a\xdf\xe2\x36\xe9\x9c\xae\x73\x3b\x6c\x19\x13\xb8\x39\x9c\x19\x7a\x9d\xfb\x1b\x58\xe6\xea\xfe\xa6\x6d\xc2\xca\xb2\x31\xe4\xd6\x39\x54\x00\x61\x4c\x16\x0b\x60\x99\xf5\x7d\x07\xe0\x34\x06\xbd\xe3\x78\xcf\x9b\x40\xe5\xd2\xb7\x06\x86\x0a\xd1\xb9\x82\xf7\xe0\x35\xaf\x82\xe2\x3c\xcb\x73\x05\x8f\xc3\x98\x15\x32\xc0\x71\xd6\x63\x05\x03\x1d\x91\xb8\x90\x8e\x29\x1b\x3e\xe4\x81\xa2\x14\xf7\xde\xa2\x7c\x1f\xbc\x25\xe7\x66\x00\x59\x0e\x68\x88\x0e\x62\xc9\xe9\x3c\xe8\x76\xf5\x37\xea\x64\x77\xbe\x28\x54\x4e\xc6\x62\xf8\x69\xb1\x2e\x77\x94\xb5\xab\x26\x12\xd2\x61\x1a\xc9\x56\x7f\xcb\x2e\xe1\xf4\x51\xdb\x27\x6d\x1e\xdd\x0f\x06\x24\x9d\x75\x35\x5b\x9d\x17\x9f\x07\xfa\x10\x55\x19\x42\x5d\xc5\xd9\x26\x5f\xe7\xea\x4a\x88\xb2\xf1\xb3\x0d\xa4\x9c\xdd\x03\xa6\xaa\xd6\x6f\xb7\x41\xd5\x14\xf7\xc0\xb5\xde\xae\x96\x5b\x50\xbe\x49\x55\xd9\x78\xa9\xd4\xd6\xe9\x5a\x50\x27\x89\x1b\x21\xac\xa2\x3b\x37\xac\xf1\x11\x45\xcd\x55\xfb\xfd\x16\x8b\xeb\x9d\xdc\x5a\x7f\xe2\xe7\x54\xee\x9b\xc2\xba\xe2\xef\x3e\x0d\xb8\x99\x98\xe9\xb8\xb1\xb3\xe8\xdf\xb7\x50\xaf\xdc\xbc\x53\xae\x53\x26\x81\x83\x90\x94\x4d\xcb\x62\xe9\x43\x99\xf9\x8b\x04\x5d\xe8\xd5\xf5\x83\xe0\xe4\xe9\xc5\xa0\xf7\xf4\xf2\xee\xe4\x62\xd0\xfb\xb7\xcb\x8b\x41\xef\xe7\xcb\xbb\x8b\xc1\xf1\xe5\x0b\xfd\x53\xff\x79\x11\x8e\xe2\x7f\x0e\x5d\xd8\x9f\xce\x69\x64\x54\xbd\x20\xbd\xaf\x67\xbd\xff\x1e\xf4\x7e\x8e\xff\xf2\xf8\xe0\x87\x7f\x79\x72\xd4\x1f\xbe\xf8\xdb\xd5\xff\x7c\xbb\xdb\xfc\x6f\xef\xf2\xe8\xdf\xeb\xf9\xcb\xe0\x45\x52\x3f\xf5\x2e\xbf\x0d\xa2\x1f\x8f\x37\xce\x7c\xf8\x22\x78\x91\x8c\xe2\x07\x71\x84\x4f\x3c\x6d\x82\xd1\xea\x49\x32\xea\x8f\xfa\x61\x70\x31\xca\x48\xef\xeb\x28\xee\x5d\x1e\xa9\x95\x5d\xe8\x87\xcb\x6f\x27\xd1\x8f\x9b\xd6\x0a\x26\x83\xde\xcf\xa3\xde\xe8\x60\xd4\xbf\xfc\x76\x32\x88\x36\xde\xfc\x52\x00\xd7\xf5\xb2\x3b\x28\x20\xe5\x20\xbd\xa1\x05\x11\x62\x15\x14\x3c\x7c\x91\x79\xe3\x29\x87\x2c\x10\x77\xc0\x54\xce\xee\x8b\x26\xfa\x7d\x7b\x70\x75\xd7\xbb\x8b\xc3\x17\xb2\xb8\x06\x56\xcd\x5f\x6e\x6d\x26\x55\x39\xc4\x0d\x85\xd5\x15\x27\x2b\xdb\x50\xfa\x48\x56\x36\x55\xb0\x9f\xab\x75\x71\xcc\x60\x9d\x2d\xe7\x0b\xcb\xf5\x0e\xd6\xaf\x96\xf3\x85\xc7\xb9\xfb\xad\xf1\x77\xf4\x95\xcc\x97\x49\xb0\x42\x2f\x73\xba\x18\x17\x84\x67\xff\xf1\x29\x38\x8c\xc7\x92\x1d\x46\xf5\xcb\x24\xdb\x87\x4b\x90\xcd\x50\xe2\x29\xc8\xd7\x39\xa8\x9f\xbf\xdc\x9e\x67\xc1\xa1\x77\xb3\x0e\x43\xaf\xc4\xec\x6a\x23\x35\x0c\xb3\xa5\x17\xdd\x32\xa9\xeb\x84\xca\x78\x8a\x3b\x2a\x11\xcf\x9e\x0d\x6f\xd7\xe6\xd2\x2a\xeb\x97\x12\x0e\x4f\xd9\xf0\xee\x24\x4a\xed\x96\x84\xb1\x5a\x45\xe0\xf7\x03\x1a\xfb\xb6\xff\xc2\xee\xd1\x72\xcb\xda\x76\x99\xa3\x5b\xe7\x1d\x2b\xab\x61\x1b\x0b\x9b\x82\x7c\x57\x08\x59\xb6\x54\xef\xfb\x5c\xc1\x79\x67\xf2\x07\x57\x5e\xd6\x46\x25\x3c\xa5\x72\xb6\x1c\xe3\x50\xbf\xb0\x54\x91\xc9\x76\xdb\xde\x96\x13\xde\x71\x51\x83\xbf\x92\x31\x76\xbe\xee\x5a\xb2\x94\xc8\xef\xfa\xbe\xab\xd4\xac\xeb\xfb\x30\x37\xf7\xb1\x9f\x6d\xd5\xad\xaf\xe3\xa7\x83\x56\xb7\xab\x6a\xd9\x19\xf2\xae\x96\x69\x93\xc6\xf9\x5e\x4d\x41\xea\x26\xdf\x9f\x7f\xff\x47\xbd\xb8\xfb\x3e\xfb\x72\x13\xc9\xce\x16\xaf\x83\xf4\x0b\x65\x84\xdf\x3a\x20\xaa\xa0\x6a\x00\xf5\x2f\x46\xeb\xc1\xa0\x37\x5a\x0f\x7e\x1a\xad\x07\xaf\x7b\xa3\xf5\xf1\x9b\xcb\xbe\xfe\xa6\xab\x24\xaf\xf0\x66\x74\x3a\xcb\xe9\x74\x56\xbe\x09\x77\x23\xa4\x7b\xb8\x67\xe4\x56\x48\x92\x5e\x7b\xce\x68\x6b\x4c\x8d\x27\x05\x7f\xed\xe5\x79\xb6\xcf\x56\x19\xdb\x02\xa2\x61\xf5\xb3\xea\xcf\x19\xe2\x08\xe1\x67\x73\xc2\xaf\x9f\x1f\x1c\x3f\xeb\xeb\x1f\x7e\x9d\x54\x2d\xd6\x02\xd4\x4d\xec\x66\x0d\xb7\xe7\xa9\x3e\xd3\x24\xfa\x23\x59\x84\x5f\x41\x0e\x12\xb0\x9f\xa1\x9a\x8b\x86\x86\x08\x3f\xcb\xe8\x0d\x4a\xd5\xe5\x1c\x1e\x92\x1c\xb8\x44\xfa\x6f\x8f\xb2\x49\x71\x88\x78\x91\x83\x19\x3f\x7c\xae\xd3\x23\x93\x99\x16\x0c\xfd\x20\x90\x2c\x90\x00\xb0\x70\x02\x15\x13\x94\x69\x79\x99\x6e\x8f\x8b\xf8\x59\x3f\xa3\x37\xcf\xb1\x9b\x93\xce\x0a\x21\x9d\xcf\x02\xed\x8d\xf5\x13\xd7\xf2\x8d\xdc\x9b\x25\x4b\xd1\xb0\xbd\xe8\x1d\xae\xc3\x7d\x1d\x53\x86\x18\x33\x53\x6d\x0b\xfe\x41\xe0\x48\x6b\xd1\xf9\xe6\xcf\xa2\x1f\xfa\x05\x3b\x7a\xac\x5c\x5a\x4f\xc9\x8a\xda\x41\xb4\x39\x64\x5c\xd4\xa1\xe3\xf9\x0e\x33\x2a\x54\xfe\x98\x1d\xaa\x84\xd1\x4d\x48\x5b\x6b\x11\x0b\xca\x18\x70\x6f\x29\x4a\xd1\xdf\x97\xd2\x68\x1a\x39\x06\x0a\xc2\x1d\x45\x4c\xb5\xd9\x6b\x6b\x73\xb7\xb9\x5b\x7e\x1e\xe3\x36\x09\x3a\x2f\x63\x09\xb3\x2a\x78\xf9\xb9\x89\x0a\xc4\xff\xa5\x1f\x02\xdc\xff\x42\x6e\x88\x48\x39\x5d\x48\xd1\xaf\xee\xe0\x55\x49\x1b\x7f\x11\xb5\x4d\xcd\x50\xc1\x6a\x9f\xb7\xad\xc5\xf0\x5d\x06\xb9\x8a\x75\x2f\xa2\xf3\xac\x34\x0f\xbd\x5e\xfd\x0e\x8f\x51\x2a\x14\x57\x1e\xe6\x9e\x33\xd7\x38\x69\x1e\x8b\x32\xd6\xbb\xf2\x34\x68\x9b\x46\x9e\x5a\x5e\xd6\x82\x3b\x62\x5c\xe4\x11\x8f\x89\x80\x04\xe1\x19\xac\x1b\x13\xfa\xdb\x8b\x04\xfd\xd4\x20\xbf\x95\xf0\x96\x17\xcb\x85\xae\xfa\x8f\xfd\x49\xa5\x71\xa2\xbf\x77\xf5\xc7\x89\x48\x29\xed\x9a\xc8\x29\x83\xf7\xcb\xf9\x18\xb8\xe8\x9a\x16\xf2\x36\x87\xa4\xb1\x3a\x97\xeb\x57\x98\xc8\x04\x1d\x1e\x46\x5b\x29\x3e\xaa\xdd\x48\xd0\x61\xd2\xa2\x11\x7a\x5f\x0c\xc2\xdd\x96\x69\xcb\xde\x9e\x9f\xc1\x7a\x9b\xf4\x19\xac\x2d\x5f\xd7\xdc\xfb\x65\x9e\x27\xe8\x30\x6e\xcd\xb1\x82\x7d\xe0\x94\xe9\x92\xb0\x93\xa0\xd4\x69\x0b\xff\xc6\x79\xda\xec\x73\xc4\x5a\x47\xbf\xcb\x69\x79\xff\x37\x85\x32\xbc\x96\xf7\x38\x6c\x6c\x4b\xd9\x2f\x6f\xa7\x81\x7e\x23\xb8\x55\x61\x7b\xac\x4e\x5a\xdc\x60\xab\x5b\x9b\x91\xf5\x3d\x61\xa3\x06\xaf\xdc\xc1\xa2\x10\x55\x8a\xe3\x5c\xb7\x4d\xd4\xe5\xf4\xbf\xc7\x4d\xfe\xbf\x42\xc7\xd6\xe0\xb8\x22\x9c\x51\x36\x6d\xc4\x47\x15\xaa\x91\xa0\x5f\x01\xc9\xa2\x40\x39\xe1\x53\xf5\x0b\x65\x54\x2c\x72\x72\x8b\x28\x53\x47\x3d\x46\x3a\x8c\x2a\xc9\x2a\x88\xbe\xa5\xf2\xdd\x72\x6c\xe3\xe4\xb6\xbd\xdd\xf8\x9d\x55\xdd\xbb\xf8\xbf\x00\x00\x00\xff\xff\xdd\x8d\xff\x65\xba\x34\x00\x00") +var _staticJavascriptsApplicationJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x5b\xef\x72\xdb\x38\x92\xff\x9e\xa7\xc0\x22\x9e\x33\x19\x53\x94\xec\xbb\xcc\xce\xd0\x51\x72\x9e\xfc\xf5\xd5\x4c\x26\x95\x64\xee\xaa\xce\xf2\xfa\x20\xb2\x25\x21\xa6\x40\x15\x00\x59\x72\x62\x5d\xed\xd3\xec\x83\xcd\x93\x5c\x01\x04\x48\x80\xa4\x64\x39\xf7\x61\xa7\xb6\x1c\x11\xe8\xfe\x75\xa3\x01\xf4\x3f\x72\x6f\x08\x47\x9f\x24\x91\x02\x0d\xd1\x2f\x24\xbd\x1e\x17\x0c\xe2\xdf\x8a\x0c\xf2\x18\xd6\x12\x58\x16\x7c\x7b\x84\xd0\x92\xe7\x09\xc2\x7d\xa1\x08\x71\xf4\x08\xa1\x0c\x26\x64\x99\x4b\x91\x20\x35\x8d\x10\x56\x18\x4b\x81\x13\x64\xfe\xc3\x94\x51\x49\x49\x4e\xbf\x52\x36\xd5\x2c\x25\x11\x97\x90\x9d\x49\x43\xc7\x96\x79\x6e\xa6\xde\x50\x46\xc5\xac\x9e\x73\xa6\x3e\xf0\x62\xca\x41\x54\xe0\x03\x33\xfe\x99\xf0\x29\xc8\x5a\xa6\x1d\xff\x08\x8b\x42\x50\x59\x70\x0a\x7a\xd2\x8e\xbf\x2c\xe6\x73\xda\x41\xff\x86\xe6\xe0\x68\xee\x8c\xb3\x8c\xb2\xa9\x2f\x77\xa3\xfe\x50\x61\xd5\x4d\xd0\x64\xc9\x52\x49\x0b\x16\x84\xc6\x14\x1c\xe4\x92\x33\x24\x67\x54\xc4\x53\x90\x81\x35\x4d\x88\x86\xc3\x21\xc2\x13\xc3\x89\x4f\x2d\x5a\xb6\xe4\x44\x21\x74\x60\xd1\x09\x0a\x3c\x20\x63\xbe\x12\x4b\xd9\xc8\x52\x56\x72\xf1\x60\x90\xe8\xff\x69\x01\x08\x6d\xf4\x5f\xb5\xcd\xc0\xb2\xd3\xea\x41\x28\x2c\x34\x44\xaf\x88\x84\x78\x41\xb8\x80\x6e\x41\xe1\xa9\xaf\x48\xbd\xf4\x20\xac\x65\x03\xcb\xb6\x61\x39\x1b\x6b\xc1\x36\x08\x72\x01\x5d\xcc\xac\x58\x05\x61\x53\xef\x39\xcd\x73\x2a\x10\x42\x43\x4d\xda\x2b\x75\x77\x96\x02\x69\xc1\x32\xa1\xe6\x7f\x23\x72\x16\x4f\xf2\xa2\xe0\x81\xe1\xea\xa3\xe3\xc1\x60\x10\xd6\xd4\xca\x68\x4a\x16\x1a\x22\x06\x2b\x2d\x36\xd0\x86\x2c\x49\xec\x74\x2c\x40\x7e\x2a\x81\x03\x23\xc0\x50\x18\x3b\x57\x84\xb2\x38\xff\xf4\xfb\x27\xc9\x29\x9b\x06\x61\x2c\x96\x63\x21\x79\x70\x7c\x1c\xa1\x9f\x42\xb3\xc5\x9b\xf0\xf4\xd1\x8a\xb2\xac\x58\xc5\xc2\x5c\x35\x25\x5a\x5f\xbb\xd3\x47\x8f\x94\x56\xe6\xac\xed\xbc\x84\x34\x3b\x93\x92\xd3\xf1\x52\x42\x82\xf0\x79\xa6\x6f\x95\x04\x21\xd5\x01\x3e\x67\x19\x4d\x89\x2c\xb8\x48\xd0\x05\x56\xa3\x38\x42\xf8\x4a\x2c\x20\x55\x3f\x26\x74\x2d\x97\x1c\xd4\xcf\x79\x91\x5e\xab\x7f\x85\x5c\x8e\xf5\x14\xb9\xd6\xe3\x19\xcc\x0b\x3d\x4e\xe6\x8b\x1c\xf0\xa5\x42\x17\xb3\x82\xcb\xf2\xde\xbc\x23\x62\xb6\xcf\x69\xaf\xa9\x71\x65\x8d\x41\x84\xfe\x1a\x56\xe7\x5d\x72\x3a\x9f\x43\x56\x12\xfe\x06\x42\x90\x29\x74\x20\xeb\xad\x2f\x67\xd1\xb0\x25\xc0\xf0\x29\x19\x8b\x9c\xca\x00\xf7\xd4\x7f\xaf\xdf\xbf\x42\x1f\xde\x7e\x40\x9f\xce\xdf\xbe\x3f\xfb\xfc\xc7\xc7\xd7\x7a\x14\x47\xe8\x24\x8c\x17\xc5\x22\xf0\xb7\xd0\xa0\xc7\x1c\x16\x39\x49\x21\xe8\xff\x6d\x24\x46\xe2\x49\x3f\x42\x18\x87\xf5\xa8\x1e\x3c\x28\x47\x6b\x0f\xf0\x19\x84\xfc\x08\x39\x91\x9d\x4e\x40\x29\xbf\x20\x72\xe6\x69\xae\xf6\xe9\x03\x91\xca\x30\xb2\xf8\xb5\x58\x01\x7f\x49\x04\x58\xa5\x26\x05\x47\x81\xe2\xa3\x68\x88\x06\xa7\x88\xa2\x67\x25\x6f\x7b\x8b\xe3\x1c\xd8\x54\xce\x4e\x11\x3d\x3a\xaa\x2f\xa1\xba\xa3\x4a\x66\x4c\x59\x06\xeb\xdf\x27\xc1\x16\xee\x0b\x7a\x19\xa2\xe7\xa8\x77\x5c\xb3\xd6\xfb\xc8\x97\x70\x6a\x06\x37\xce\x3d\x34\xd3\x13\x92\x0b\xa8\x36\x72\x42\x73\x78\x59\x30\x09\x4c\x8a\x3f\x54\x84\xd8\x76\x3a\x2e\x70\x7f\xa2\x9d\x6c\xe4\x58\xa3\x72\xd3\xb7\xbf\xaf\x18\x70\x1c\x76\x4f\xbe\x27\x73\xf0\xe7\xdc\x13\x16\x75\x9a\xf7\x32\xfe\x52\x50\x16\xe0\x3e\x0e\x3b\x95\x75\x34\x4d\x49\x9e\x8f\x49\x7a\x1d\x21\xe0\xbc\xe0\x56\xf1\x83\x98\x7c\x21\xeb\xc0\xda\x47\xc7\x3f\x2d\xa9\xb1\xe6\x20\x8c\x0c\x89\x58\xa6\x29\x08\x91\xa0\x0a\xd1\xba\x37\x85\x9b\x94\xff\x94\x16\x75\xfd\x82\x7b\xfb\xbd\x18\xfc\xb2\xc8\x73\xd0\x3a\x76\x04\xe2\x89\x0d\x4d\x4a\xc8\x5c\x39\x8a\xc4\x82\x18\x58\xe3\x6f\x26\x35\xb2\x72\x39\x56\x50\x60\x25\x6b\x1f\xf4\x9f\x14\x56\xae\x68\xf5\xec\x3b\x9e\x44\xb9\x0b\x22\xc5\x55\x5a\x30\x49\xa8\xda\x2e\x47\xb2\x9e\x52\xcf\x8b\x22\xcf\x29\x9b\x7e\xa6\xe9\x35\xf0\xa4\x8a\xe1\x36\xc0\x35\xc7\x0d\xf9\x39\x93\xc0\x6f\x48\x9e\xa0\xa7\x03\x1d\x63\xab\xd4\xa1\xcb\x2d\xe8\x5d\xc8\xa9\x90\xc0\x3e\x17\xe5\x11\xd7\x6a\x44\x08\xa7\x33\xc2\xa6\x60\x0f\x19\x07\x96\x01\x0f\x6b\x26\x1d\x36\x5e\x79\xba\xd8\xbb\x57\xcf\x7f\x28\x75\x0a\xea\x83\x53\xe2\xec\x0a\xce\x5a\xfe\x96\xc8\x68\x90\x8b\x85\x07\xec\xcd\x74\xab\xb4\xe9\x92\x31\x23\xe2\xa5\x5e\x64\x16\xd4\x69\x51\x53\xda\x72\x91\x11\x09\x76\x7a\x6f\xbc\x2a\xdd\xe9\xc6\x73\x8f\xce\x9e\x78\xea\xc6\x6f\x03\xcb\x61\x7f\x24\x9b\xb8\x75\x63\x99\xd9\xbd\xd1\xbc\xf4\xb0\x1b\xd2\x25\xd9\x1b\xd7\xa6\xa3\xdd\x90\x66\xd6\x45\xd3\xa7\xcb\x3d\x74\xdb\x4e\xbb\x77\xad\xd0\x10\x09\x90\xf6\xce\x04\x2d\x0e\x54\x5e\x47\x7d\x87\x4b\x25\x27\x20\xd3\x59\x25\x38\xf2\x30\x2d\x4e\x7d\xdc\x9d\xb3\xba\xeb\xcc\xfb\x3a\xfd\xa5\x95\x8d\xa6\x39\x10\x5e\x69\xd9\x66\xe9\xb4\xc3\xab\x86\xa3\xe8\x36\x87\x4f\xf5\x10\x7b\x94\x5b\x61\xf9\x83\xd0\x5a\xa4\x4a\x11\x2b\x0b\xec\xa7\x49\x13\xaf\x91\x2b\xfb\x7e\x6f\x3f\x23\xf9\x3c\x4d\x2b\xf9\x02\x3b\xd4\x3a\x08\xf0\xe3\x94\xf0\xec\xca\xe2\x5c\xdd\x90\x7c\xa9\xd2\x24\x09\x6b\xe9\x1e\xdd\xac\xd2\xba\x5e\xb9\xef\x39\xb6\xa4\x35\x42\x97\x33\x36\xb1\x29\x9f\x3e\x17\xef\x96\x73\x52\x59\xe0\x20\xc0\x92\xca\xbc\x12\x8b\xdf\x52\xc9\x8b\x71\x82\x30\x3a\x32\xfc\x35\xe5\xe3\x85\x91\x77\x35\x26\xdc\x72\x18\xa2\x38\x15\x22\xc0\x2b\x9a\xc9\x99\x75\xeb\xa5\xf6\x3a\xe0\xd7\x1e\x10\x1d\x21\xfc\x03\x6e\xda\x7f\x97\x5f\xee\x10\xcc\x61\x5e\xdc\xc0\xcb\x9c\x28\x99\x76\xae\x37\x26\xbc\x47\x18\x9d\xab\x44\x0f\x79\xa3\x42\x72\xba\x80\x0c\x37\xb4\xc4\xc7\x83\x41\xa5\x4b\x63\xe7\xac\x13\xdd\xb5\x73\x36\x64\x57\x3b\x37\xa3\x19\x04\xed\x0d\xb4\xd5\x95\x71\xda\x3a\xa5\x4c\x49\x0e\xb6\x14\x09\xe3\x09\xc9\xe0\x9c\x05\x78\x42\x84\xc4\xcd\x5d\xd6\x2e\x78\xb7\x1e\x39\xec\xab\x84\xf6\xf4\x0f\xd5\xc0\x38\xee\x5d\x3a\xa4\x25\xc9\x5e\x5a\x54\x51\xe2\xa1\x7a\xb8\xde\x7e\x97\x32\xdc\xa1\xdb\x4b\x23\x3f\xd2\x3c\x54\x2d\x13\x31\x76\x69\x24\x4b\x92\xbd\x94\xa9\xc2\xd3\xfe\x7a\x78\x77\x7b\xa7\x37\x28\x0f\xbb\x58\x51\x15\x69\x9a\x92\x6d\xff\xc3\xb2\xa5\x44\x40\xa3\x3f\x94\x38\xae\x5a\xfb\x16\x7c\xee\x4e\xdb\x94\x69\xcc\x81\x5c\x9f\x3a\x20\x53\x22\x67\xc0\xbb\x11\xde\xda\x39\xe4\x6e\xdc\x76\x2c\xc2\x48\x7e\xbb\x45\x9b\x33\x3b\xe7\x63\x6d\x83\xaa\x7a\x3c\x6d\xa4\x37\x6e\xfb\xa7\xc1\x6c\x9a\x6a\x6d\xa6\x3f\xd8\x35\x2b\x56\xac\x8b\xc7\x2b\xcf\x0c\xc7\x11\xc2\x28\x50\xae\x56\xf7\x62\xce\x59\xfb\x30\xb8\xb9\xa3\x72\x9d\x61\xd9\x8d\x6a\x75\x2a\x4c\x65\x50\x75\x2b\xd4\x73\xf0\x4d\xe5\xfc\xea\x0c\x36\x4b\x82\xb0\x59\xd0\xdc\x57\x58\x48\x32\x55\xe5\x5d\x82\xb0\x2c\x0b\x0a\xb8\x29\xcb\x33\xd3\x54\x4c\x73\x9a\x5e\x23\x99\xc5\x69\x91\xf7\x74\x39\x4d\xb0\x2a\x45\x66\xc5\xca\x48\xc0\x55\x4f\x4e\xc2\x7c\xa1\xaa\xf1\x04\x5d\xc5\xf6\x77\xa0\xb4\xb4\x0f\xd6\xb1\xaa\x7b\x22\xe7\x79\x10\x86\x3b\xb3\x7b\x6d\xb2\x03\x95\xe3\x29\x62\x53\x4a\x1b\x58\xc7\x9c\xc4\xb6\x64\x44\x18\xc6\x19\x91\x24\xc0\x56\x8e\x1b\xb0\xb6\x85\x26\xa7\x8b\xd0\xaa\x1a\x94\x70\x92\x65\x26\x20\xa9\x3a\xbe\xc7\x4b\x52\x1c\x76\x6c\xbe\xe2\xa9\xab\xdd\x82\xcf\x89\x94\x90\xd9\xa2\x78\xdb\xf5\x5d\xe4\x54\x0a\xa4\x9b\x6b\x5d\x6e\xdd\xb4\x2b\x4c\x8f\xa5\x8f\x9d\x46\x9a\x8a\x0f\x8c\xcc\x41\xb1\x96\x30\x6e\x8b\x45\x51\x64\x94\x43\xaa\x6a\x78\x0b\x0e\x79\x4e\x17\x82\x0a\xfa\x15\x02\xc3\x52\x15\xea\x11\xfa\x71\x10\xa1\x93\xa7\x8e\xa5\x1c\xfe\xe1\x10\x61\xdc\xee\x75\x3e\x13\x92\x17\x6c\xfa\x5c\x1d\xf6\xab\x18\x44\x4a\x16\x10\x58\xc5\xf4\xd1\x7e\xd6\xb7\x24\x1d\x26\xab\x58\x2a\x49\x9a\xa7\x8f\x35\xe7\x03\xb1\xb5\xdd\x9d\x15\x3a\x16\x17\x92\x47\x68\x4e\xd9\xaf\xba\x6d\x13\x21\xc8\xa6\x50\xfe\xb6\x4b\x12\x52\x25\xb2\xc6\x23\x0b\xc9\x1d\x2b\x08\xc9\x4d\xbf\x07\x3d\xab\x41\xd0\xdd\x1d\x72\x67\x86\x28\xa8\x51\xd1\x13\x74\x12\xb6\xac\x25\x24\x6f\xb5\x84\xb3\x29\x94\x34\x43\x74\xc6\x39\xb9\x75\x41\x8e\xd0\x71\x68\xf6\x27\x76\x37\x7e\x4e\x33\x43\x31\x74\x55\xe8\x21\x5f\x81\x53\xb7\x11\x26\x81\x33\x2d\x05\x6b\xc7\xa4\xe5\x1e\x21\x1c\xc6\xdf\xd4\x63\x8d\x78\x84\xf0\xc6\xa7\xc0\xa7\xbe\x87\xe3\x55\x63\x4e\x79\xa5\x8f\x30\x7d\xbd\x5e\x04\x46\x42\x18\x21\x7c\x70\xfc\xe7\xdf\xff\x71\x70\xe2\x86\xb1\xda\x5d\x38\x7b\x02\xd6\x3e\x10\x2f\xb8\xf6\x3b\xaf\x4a\xf7\xeb\xf5\x04\xe6\x84\x5f\x9f\x89\x4f\x90\x43\xaa\xaf\xa8\x63\x85\x22\x23\xb9\xe3\x1f\x8d\x84\xdf\xd4\x70\xd5\x37\x32\x0d\x12\xa7\x4b\x61\x9b\x42\x79\x82\xf0\x63\xe3\x29\xae\x34\x16\x8a\xf5\x3f\xbd\xb4\xec\x2e\x61\xa7\x57\x84\x6a\x69\xa6\xad\xe1\x64\xda\x3e\x0a\x0e\x4b\x98\xa0\xc5\xa8\xcb\xc0\x37\x4e\xfb\xca\xe9\x71\xf8\xcb\xdc\xe5\x0d\xd3\xbc\x10\x20\x64\x80\xe5\xb8\xc8\x6e\x71\xa8\x3b\x4c\x01\x96\x3c\x96\x64\x9c\x43\x4f\x18\x8c\x66\x3e\xdd\x9c\x3d\x7d\xb4\xcd\xcf\x75\x10\x76\xf5\xca\xee\x8b\x2d\x69\xd5\x3f\x4b\x90\x4d\xa9\xbf\xa7\xb9\x54\xe3\x44\x08\x93\x2c\xf3\xdb\x4b\x46\x1b\x77\x39\x15\x7b\xd9\x16\xb3\x6d\xa9\xa4\x4a\xd5\x23\x74\x15\x67\x30\x2e\x96\x2c\x35\xa1\xa4\x4c\xf8\x22\xf4\x74\x30\x08\xdb\x1b\x2b\xae\x04\x10\x9e\x2a\x3f\x5c\xb0\x00\x5f\xc3\xed\x72\xd1\x01\x52\x12\x59\x29\x11\x3a\xe9\x04\xab\x4e\x89\x82\x52\x37\x23\x1e\x8b\xf2\xc4\xe0\xc8\xb9\x1c\xea\x3e\xb8\xc5\x52\x56\xa4\xcb\xb9\x1a\xb3\x2a\x64\x2a\x1f\x89\x3a\xae\x93\x93\x08\x42\x7c\x0d\xb7\x2f\x8b\xcc\x9b\xd3\x19\xd2\xbf\xfe\x35\xa9\x06\x6c\x34\xb1\x2f\x40\x26\xce\x06\xeb\xab\x49\x8b\xa5\x30\xcb\xaa\xbb\x67\x8d\x34\xa8\x46\xfe\x79\x4f\x64\x06\x6b\xb9\x0f\x6a\x23\x29\xab\x7d\x51\x4d\xb2\xa9\x7e\x29\x7f\x6d\xa4\x58\xb7\xa8\x42\xd7\xc0\x35\xc0\x2e\x7e\x4f\x43\x92\x4a\x7a\x03\x95\x8e\xfb\xdc\x27\x07\xe3\x9e\x2b\x55\xdb\x67\x2f\x47\xe6\x38\x33\x8b\xef\x27\x3b\x55\x1f\xfc\x21\xde\xcd\xf5\x70\xbb\xbc\xdc\xb6\x33\xec\x79\x3a\xb4\x8f\xb7\x73\x25\x6e\xca\xfe\x8f\x3e\xd1\x33\x9a\x65\xc0\x1e\x7a\x17\x96\x6c\xac\xbd\x9f\xbd\x0f\x15\x70\xa3\x94\xdb\xe6\x69\x6a\xdf\xe2\x36\xe9\x9c\xae\x73\x3b\x6c\x19\x13\xb8\x39\x9c\x19\x7a\x9d\xfb\x1b\x58\xe6\xea\xfe\xa6\x6d\xc2\xca\xb2\x31\xe4\xd6\x39\x54\x00\x61\x4c\x16\x0b\x60\x99\xf5\x7d\x07\xe0\x34\x06\xbd\xe3\x78\xcf\x9b\x40\xe5\xd2\xb7\x06\x86\x0a\xd1\xb9\x82\xf7\xe0\x35\xaf\x82\xe2\x3c\xcb\x73\x05\x8f\xc3\x98\x15\x32\xc0\x71\xd6\x63\x05\x03\x1d\x91\xb8\x90\x8e\x29\x1b\x3e\xe4\x81\xa2\x14\xf7\xde\xa2\x7c\x1f\xbc\x25\xe7\x66\x00\x59\x0e\x68\x88\x0e\x62\xc9\xe9\x3c\xe8\x76\xf5\x37\xea\x64\x77\xbe\x28\x54\x4e\xc6\x62\xf8\x69\xb1\x2e\x77\x94\xb5\xab\x26\x12\xd2\x61\x1a\xc9\x56\x7f\xcb\x2e\xe1\xf4\x51\xdb\x27\x6d\x1e\xdd\x0f\x06\x24\x9d\x75\x35\x5b\x9d\x17\x9f\x07\xfa\x10\x55\x19\x42\x5d\xc5\xd9\x26\x5f\xe7\xea\x4a\x88\xb2\xf1\xb3\x0d\xa4\x9c\xdd\x03\xa6\xaa\xd6\x6f\xb7\x41\xd5\x14\xf7\xc0\xb5\xde\xae\x96\x5b\x50\xbe\x49\x55\xd9\x78\xa9\xd4\xd6\xe9\x5a\x50\x27\x89\x1b\x21\xac\xa2\x3b\x37\xac\xf1\x11\x45\xcd\x55\xfb\xfd\x16\x8b\xeb\x9d\xdc\x5a\x7f\xe2\xe7\x54\xee\x9b\xc2\xba\xe2\xef\x3e\x0d\xb8\x99\x98\xe9\xb8\xb1\xb3\xe8\xdf\xb7\x50\xaf\xdc\xbc\x53\xae\x53\x26\x81\x83\x90\x94\x4d\xcb\x62\xe9\x43\x99\xf9\x8b\x04\x5d\xe8\xd5\xf5\x83\xe0\xe4\xe9\xc5\xa0\xf7\xf4\xf2\xee\xe4\x62\xd0\xfb\xb7\xcb\x8b\x41\xef\xe7\xcb\xbb\x8b\xc1\xf1\xe5\x0b\xfd\x53\xff\x79\x11\x8e\xe2\x7f\x0e\x5d\xd8\x9f\xce\x69\x64\x54\xbd\x20\xbd\xaf\x67\xbd\xff\x1e\xf4\x7e\x8e\xff\xf2\xf8\xe0\x87\x7f\x79\x72\xd4\x1f\xbe\xf8\xdb\xd5\xff\x7c\xbb\xdb\xfc\x6f\xef\xf2\xe8\xdf\xeb\xf9\xcb\xe0\x45\x52\x3f\xf5\x2e\xbf\x0d\xa2\x1f\x8f\x37\xce\x7c\xf8\x22\x78\x91\x8c\xe2\x07\x71\x84\x4f\x3c\x6d\x82\xd1\xea\x49\x32\xea\x8f\xfa\x61\x70\x31\xca\x48\xef\xeb\x28\xee\x5d\x1e\xa9\x95\x5d\xe8\x87\xcb\x6f\x27\xd1\x8f\x9b\xd6\x0a\x26\x83\xde\xcf\xa3\xde\xe8\x60\xd4\xbf\xfc\x76\x32\x88\x36\xde\xfc\x52\x00\xd7\xf5\xb2\x3b\x28\x20\xe5\x20\xbd\xa1\x05\x11\x62\x15\x14\x3c\x7c\x91\x79\xe3\x29\x87\x2c\x10\x77\xc0\x54\xce\xee\x8b\x26\xfa\x7d\x7b\x70\x75\xd7\xbb\x8b\xc3\x17\xb2\xb8\x06\x56\xcd\x5f\x6e\x6d\x26\x55\x39\xc4\x0d\x85\xd5\x15\x27\x2b\xdb\x50\xfa\x48\x56\x36\x55\xb0\x9f\xab\x75\x71\xcc\x60\x9d\x2d\xe7\x0b\xcb\xf5\x0e\xd6\xaf\x96\xf3\x85\xc7\xb9\xfb\xad\xf1\x77\xf4\x95\xcc\x97\x49\xb0\x42\x2f\x73\xba\x18\x17\x84\x67\xff\xf1\x29\x38\x8c\xc7\x92\x1d\x46\xf5\xcb\x24\xdb\x87\x4b\x90\xcd\x50\xe2\x29\xc8\xd7\x39\xa8\x9f\xbf\xdc\x9e\x67\xc1\xa1\x77\xb3\x0e\x43\xaf\xc4\xec\x6a\x23\x35\x0c\xb3\xa5\x17\xdd\x32\xa9\xeb\x84\xca\x78\x8a\x3b\x2a\x11\xcf\x9e\x0d\x6f\xd7\xe6\xd2\x2a\xeb\x97\x12\x0e\x4f\xd9\xf0\xee\x24\x4a\xed\x96\x84\xb1\x5a\x45\xe0\xf7\x03\x1a\xfb\xb6\xff\xc2\xee\xd1\x72\xcb\xda\x76\x99\xa3\x5b\xe7\x1d\x2b\xab\x61\x1b\x0b\x9b\x82\x7c\x57\x08\x59\xb6\x54\xef\xfb\x5c\xc1\x79\x67\xf2\x07\x57\x5e\xd6\x46\x25\x3c\xa5\x72\xb6\x1c\xe3\x50\xbf\xb0\x54\x91\xc9\x76\xdb\xde\x96\x13\xde\x71\x51\x83\xbf\x92\x31\x76\xbe\xee\x5a\xb2\x94\xc8\xef\xfa\xbe\xab\xd4\xac\xeb\xfb\x30\x37\xf7\xb1\x9f\x6d\xd5\xad\xaf\xe3\xa7\x83\x56\xb7\xab\x6a\xd9\x19\xf2\xae\x96\x69\x93\xc6\xf9\x5e\x4d\x41\xea\x26\xdf\x9f\x7f\xff\x47\xbd\xb8\xfb\x3e\xfb\x72\x13\xc9\xce\x16\xaf\x83\xf4\x0b\x65\x84\xdf\x3a\x20\xaa\xa0\x6a\x00\xf5\x2f\x46\xeb\xc1\xa0\x37\x5a\x0f\x7e\x1a\xad\x07\xaf\x7b\xa3\xf5\xf1\x9b\xcb\xbe\xfe\xa6\xab\x24\xaf\xf0\x66\x74\x3a\xcb\xe9\x74\x56\xbe\x09\x77\x23\xa4\x7b\xb8\x67\xe4\x56\x48\x92\x5e\x7b\xce\x68\x6b\x4c\x8d\x27\x05\x7f\xed\xe5\x79\xb6\xcf\x56\x19\xdb\x02\xa2\x61\xf5\xb3\xea\xcf\x19\xe2\x08\xe1\x67\x73\xc2\xaf\x9f\x1f\x1c\x3f\xeb\xeb\x1f\x7e\x9d\x54\x2d\xd6\x02\xd4\x4d\xec\x66\x0d\xb7\xe7\xa9\x3e\xd3\x24\xfa\x23\x59\x84\x5f\x41\x0e\x12\xb0\x9f\xa1\x9a\x8b\x86\x86\x08\x3f\xcb\xe8\x0d\x4a\xd5\xe5\x1c\x1e\x92\x1c\xb8\x44\xfa\x6f\x8f\xb2\x49\x71\x88\x78\x91\x83\x19\x3f\x7c\xae\xd3\x23\x93\x99\x16\x0c\xfd\x20\x90\x2c\x90\x00\xb0\x70\x02\x15\x13\x94\x69\x79\x99\x6e\x8f\x8b\xf8\x59\x3f\xa3\x37\xcf\xb1\x9b\x93\xce\x0a\x21\x9d\xcf\x02\xed\x8d\xf5\x13\xd7\xf2\x8d\xdc\x9b\x25\x4b\xd1\xb0\xbd\xe8\x1d\xae\xc3\x7d\x1d\x53\x86\x18\x33\x53\x6d\x0b\xfe\x41\xe0\x48\x6b\xd1\xf9\xe6\xcf\xa2\x1f\xfa\x05\x3b\x7a\xac\x5c\x5a\x4f\xc9\x8a\xda\x41\xb4\x39\x64\x5c\xd4\xa1\xe3\xf9\x0e\x33\x2a\x54\xfe\x98\x1d\x36\xf2\xd1\xd6\x52\xc4\x82\x32\x06\xdc\x5b\x89\xd2\xf3\xf7\xa5\x34\x8a\x46\x8e\x7d\x82\x70\x47\x0d\x53\xed\xf5\xda\x9a\xdc\xed\xed\x96\x5f\xc7\xb8\x3d\x82\xce\xbb\x58\xc2\xac\x0a\x5e\x7e\x6d\xa2\xe2\xf0\x7f\xe9\x87\x00\xf7\xbf\x90\x1b\x22\x52\x4e\x17\x52\xf4\xab\x2b\x78\x55\xd2\xc6\x5f\x44\x6d\x52\x33\x54\xb0\xda\xe5\x6d\xeb\x30\x7c\x97\x41\xae\x62\xdd\x8a\xe8\x3c\x2a\xcd\x33\xaf\x57\xbf\xc3\x61\x94\x0a\xc5\x95\x83\xb9\xe7\xc8\x35\x0e\x9a\xc7\xa2\x8c\xf5\xae\x3c\x0c\xda\xa6\x91\xa7\x96\x97\xb4\xe0\x8e\x10\x17\x79\xc4\x63\x22\x20\x41\x78\x06\xeb\xc6\x84\xfe\xf4\x22\x41\x3f\x35\xc8\x6f\x25\xbc\xe5\xc5\x72\xa1\x8b\xfe\x63\x7f\x52\x69\x9c\xe8\xcf\x5d\xfd\x71\x22\x52\x4a\xbb\x26\x72\xca\xe0\xfd\x72\x3e\x06\x2e\xba\xa6\x85\xbc\xcd\x21\x69\xac\xce\xe5\xfa\x15\x26\x32\x41\x87\x87\xd1\x56\x8a\x8f\x6a\x37\x12\x74\x98\xb4\x68\x84\xde\x17\x83\x70\xb7\x65\xda\xb2\xb7\xe7\x67\xb0\xde\x26\x7d\x06\x6b\xcb\xd7\x35\xf7\x7e\x99\xe7\x09\x3a\x8c\x5b\x73\xac\x60\x1f\x38\x65\xba\x22\xec\x24\x28\x75\xda\xc2\xbf\x71\x9e\x36\xfb\x1c\xb1\xd6\xd1\xef\xf2\x59\xde\xff\x4b\xa1\x8c\xae\xe5\x3d\x0e\x1b\xdb\x52\xb6\xcb\xdb\x59\xa0\xdf\x07\x6e\x15\xd8\x1e\xab\x93\x15\x37\xd8\xea\xce\x66\x64\x7d\x4f\xd8\x72\x79\xc6\x1d\x2c\x0a\x51\x65\x38\xce\x75\xdb\x44\x5d\x3e\xff\x7b\xdc\xe4\xff\x2b\x72\x6c\x8d\x8d\x2b\xc2\x19\x65\xd3\x46\x78\x54\x91\x1a\x09\xfa\x15\x90\x2c\x0a\x94\x13\x3e\x55\xbf\x50\x46\xc5\x22\x27\xb7\x88\x32\x75\xd4\x63\xa4\xa3\xa8\x92\xac\x62\xe8\x5b\x2a\xdf\x2d\xc7\x36\x4c\x6e\xdb\xdb\x8d\xdf\x58\xd5\xad\x8b\xff\x0b\x00\x00\xff\xff\x42\x4a\x4e\x5b\xb9\x34\x00\x00") func staticJavascriptsApplicationJsBytes() ([]byte, error) { return bindataRead( @@ -296,7 +296,7 @@ func staticJavascriptsApplicationJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "static/javascripts/application.js", size: 13498, mode: os.FileMode(420), modTime: time.Unix(1583790916, 0)} + info := bindataFileInfo{name: "static/javascripts/application.js", size: 13497, mode: os.FileMode(420), modTime: time.Unix(1583791341, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/core/session.go b/core/session.go index c7ce5782..06c702c0 100644 --- a/core/session.go +++ b/core/session.go @@ -116,14 +116,18 @@ func (s *Session) AddRepository(repository *common.Repository) { func (s *Session) AddFinding(finding *matching.Finding) { s.Lock() defer s.Unlock() + const MaxStrLen = 100 s.Findings = append(s.Findings, finding) - s.Out.Warn(" %s: %s\n", strings.ToUpper(finding.Action), finding.Description) - s.Out.Info(" Path.......: %s\n", finding.FilePath) - s.Out.Info(" Repo.......: %s\n", finding.CloneUrl) - s.Out.Info(" Message....: %s\n", common.TruncateString(finding.CommitMessage, 100)) - s.Out.Info(" Author.....: %s\n", finding.CommitAuthor) - if finding.Comment != "" { - s.Out.Info(" Comment....: %s\n", finding.Comment) + s.Out.Warn(" %s: %s, %s\n", strings.ToUpper(finding.Action), "File Match: " + finding.FileSignatureDescription, "Content Match: " + finding.ContentSignatureDescription) + s.Out.Info(" Path......................: %s\n", finding.FilePath) + s.Out.Info(" Repo......................: %s\n", finding.CloneUrl) + s.Out.Info(" Message...................: %s\n", common.TruncateString(finding.CommitMessage, MaxStrLen)) + s.Out.Info(" Author....................: %s\n", finding.CommitAuthor) + if finding.FileSignatureComment != "" { + s.Out.Info(" FileSignatureComment......: %s\n", common.TruncateString(finding.FileSignatureComment, MaxStrLen)) + } + if finding.ContentSignatureComment != "" { + s.Out.Info(" ContentSignatureComment...:%s\n", common.TruncateString(finding.ContentSignatureComment, MaxStrLen)) } s.Out.Info(" File URL...: %s\n", finding.FileUrl) s.Out.Info(" Commit URL.: %s\n", finding.CommitUrl) diff --git a/matching/findings.go b/matching/findings.go index baef7646..d64fefce 100644 --- a/matching/findings.go +++ b/matching/findings.go @@ -8,20 +8,22 @@ import ( ) type Finding struct { - Id string - FilePath string - Action string - Description string - Comment string - RepositoryOwner string - RepositoryName string - CommitHash string - CommitMessage string - CommitAuthor string - FileUrl string - CommitUrl string - RepositoryUrl string - CloneUrl string + Id string + FilePath string + Action string + FileSignatureDescription string + FileSignatureComment string + ContentSignatureDescription string + ContentSignatureComment string + RepositoryOwner string + RepositoryName string + CommitHash string + CommitMessage string + CommitAuthor string + FileUrl string + CommitUrl string + RepositoryUrl string + CloneUrl string } func (f *Finding) setupUrls(isGithubSession bool) { diff --git a/static/index.html b/static/index.html index 5808c171..f1dc0935 100644 --- a/static/index.html +++ b/static/index.html @@ -134,7 +134,7 @@

+ + + <%= this.formattedFilePath() %> + + <%= + this.model.shortCommitHash() %> + <%- + RepositoryOwner %>/<%- RepositoryName %> + - + + + -