directory.go 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. package sources
  2. import (
  3. "io/fs"
  4. "os"
  5. "path/filepath"
  6. "github.com/fatih/semgroup"
  7. "github.com/zricethezav/gitleaks/v8/logging"
  8. )
  9. type ScanTarget struct {
  10. Path string
  11. Symlink string
  12. }
  13. func DirectoryTargets(source string, s *semgroup.Group, followSymlinks bool, shouldSkip func(string) bool) (<-chan ScanTarget, error) {
  14. paths := make(chan ScanTarget)
  15. s.Go(func() error {
  16. defer close(paths)
  17. return filepath.Walk(source,
  18. func(path string, fInfo os.FileInfo, err error) error {
  19. logger := logging.With().Str("path", path).Logger()
  20. if err != nil {
  21. if os.IsPermission(err) {
  22. // This seems to only fail on directories at this stage.
  23. logger.Warn().Msg("Skipping directory: permission denied")
  24. return filepath.SkipDir
  25. }
  26. return err
  27. }
  28. // Empty; nothing to do here.
  29. if fInfo.Size() == 0 {
  30. return nil
  31. }
  32. // Unwrap symlinks, if |followSymlinks| is set.
  33. scanTarget := ScanTarget{
  34. Path: path,
  35. }
  36. if fInfo.Mode().Type() == fs.ModeSymlink {
  37. if !followSymlinks {
  38. logger.Debug().Msg("Skipping symlink")
  39. return nil
  40. }
  41. realPath, err := filepath.EvalSymlinks(path)
  42. if err != nil {
  43. return err
  44. }
  45. realPathFileInfo, _ := os.Stat(realPath)
  46. if realPathFileInfo.IsDir() {
  47. logger.Warn().Str("target", realPath).Msg("Skipping symlinked directory")
  48. return nil
  49. }
  50. scanTarget.Path = realPath
  51. scanTarget.Symlink = path
  52. }
  53. // TODO: Also run this check against the resolved symlink?
  54. skip := shouldSkip(path)
  55. if fInfo.IsDir() {
  56. // Directory
  57. if skip {
  58. logger.Debug().Msg("Skipping directory due to global allowlist")
  59. return filepath.SkipDir
  60. }
  61. if fInfo.Name() == ".git" {
  62. // Don't scan .git directories.
  63. // TODO: Add this to the config allowlist, instead of hard-coding it.
  64. return filepath.SkipDir
  65. }
  66. } else {
  67. // File
  68. if skip {
  69. logger.Debug().Msg("Skipping file due to global allowlist")
  70. return nil
  71. }
  72. paths <- scanTarget
  73. }
  74. return nil
  75. })
  76. })
  77. return paths, nil
  78. }