reader.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. package detect
  2. import (
  3. "bufio"
  4. "bytes"
  5. "errors"
  6. "io"
  7. "github.com/zricethezav/gitleaks/v8/report"
  8. )
  9. // DetectReader accepts an io.Reader and a buffer size for the reader in KB
  10. func (d *Detector) DetectReader(r io.Reader, bufSize int) ([]report.Finding, error) {
  11. reader := bufio.NewReader(r)
  12. buf := make([]byte, 1000*bufSize)
  13. findings := []report.Finding{}
  14. for {
  15. n, err := reader.Read(buf)
  16. // "Callers should always process the n > 0 bytes returned before considering the error err."
  17. // https://pkg.go.dev/io#Reader
  18. if n > 0 {
  19. // Try to split chunks across large areas of whitespace, if possible.
  20. peekBuf := bytes.NewBuffer(buf[:n])
  21. if readErr := readUntilSafeBoundary(reader, n, maxPeekSize, peekBuf); readErr != nil {
  22. return findings, readErr
  23. }
  24. fragment := Fragment{
  25. Raw: peekBuf.String(),
  26. }
  27. for _, finding := range d.Detect(fragment) {
  28. findings = append(findings, finding)
  29. if d.Verbose {
  30. printFinding(finding, d.NoColor)
  31. }
  32. }
  33. }
  34. if err != nil {
  35. if err == io.EOF {
  36. break
  37. }
  38. return findings, err
  39. }
  40. }
  41. return findings, nil
  42. }
  43. // StreamDetectReader streams the detection results from the provided io.Reader.
  44. // It reads data using the specified buffer size (in KB) and processes each chunk through
  45. // the existing detection logic. Findings are sent down the returned findings channel as soon as
  46. // they are detected, while a separate error channel signals a terminal error (or nil upon successful completion).
  47. // The function returns two channels:
  48. // - findingsCh: a receive-only channel that emits report.Finding objects as they are found.
  49. // - errCh: a receive-only channel that emits a single final error (or nil if no error occurred)
  50. // once the stream ends.
  51. //
  52. // Recommended Usage:
  53. //
  54. // Since there will only ever be a single value on the errCh, it is recommended to consume the findingsCh
  55. // first. Once findingsCh is closed, the consumer should then read from errCh to determine
  56. // if the stream completed successfully or if an error occurred.
  57. //
  58. // This design avoids the need for a select loop, keeping client code simple.
  59. //
  60. // Example:
  61. //
  62. // // Assume detector is an instance of *Detector and myReader implements io.Reader.
  63. // findingsCh, errCh := detector.StreamDetectReader(myReader, 64) // using 64 KB buffer size
  64. //
  65. // // Process findings as they arrive.
  66. // for finding := range findingsCh {
  67. // fmt.Printf("Found secret: %+v\n", finding)
  68. // }
  69. //
  70. // // After the findings channel is closed, check the final error.
  71. // if err := <-errCh; err != nil {
  72. // log.Fatalf("StreamDetectReader encountered an error: %v", err)
  73. // } else {
  74. // fmt.Println("Scanning completed successfully.")
  75. // }
  76. func (d *Detector) StreamDetectReader(r io.Reader, bufSize int) (<-chan report.Finding, <-chan error) {
  77. findingsCh := make(chan report.Finding, 1)
  78. errCh := make(chan error, 1)
  79. go func() {
  80. defer close(findingsCh)
  81. defer close(errCh)
  82. reader := bufio.NewReader(r)
  83. buf := make([]byte, 1000*bufSize)
  84. for {
  85. n, err := reader.Read(buf)
  86. if n > 0 {
  87. peekBuf := bytes.NewBuffer(buf[:n])
  88. if readErr := readUntilSafeBoundary(reader, n, maxPeekSize, peekBuf); readErr != nil {
  89. errCh <- readErr
  90. return
  91. }
  92. fragment := Fragment{Raw: peekBuf.String()}
  93. for _, finding := range d.Detect(fragment) {
  94. findingsCh <- finding
  95. if d.Verbose {
  96. printFinding(finding, d.NoColor)
  97. }
  98. }
  99. }
  100. if err != nil {
  101. if errors.Is(err, io.EOF) {
  102. errCh <- nil
  103. return
  104. }
  105. errCh <- err
  106. return
  107. }
  108. }
  109. }()
  110. return findingsCh, errCh
  111. }