forked from k0sproject/rig
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreader.go
More file actions
155 lines (133 loc) · 3.5 KB
/
Copy pathreader.go
File metadata and controls
155 lines (133 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package redact
import (
"bytes"
"errors"
"io"
"sort"
"github.com/k0sproject/rig/v2/byteslice"
)
type redactReader struct {
r io.Reader
matches [][]byte
buf *bytes.Buffer
out *bytes.Buffer
mask []byte
isEOF bool
maxLen int64
}
// Reader returns a new io.Reader that will redact any matches of the provided strings with the provided mask.
func Reader(r io.Reader, mask string, matches ...string) io.Reader {
matchBytes := make([][]byte, len(matches))
var maxLen int
for i, match := range matches {
matchBytes[i] = []byte(match)
maxLen = max(maxLen, len(matchBytes[i]))
}
return &redactReader{
r: r,
matches: matchBytes,
buf: &bytes.Buffer{},
out: &bytes.Buffer{},
mask: []byte(mask),
maxLen: int64(maxLen),
}
}
// Read implements the io.Reader interface.
func (rr *redactReader) Read(p []byte) (int, error) {
if rr.isEOF && rr.out.Len() == 0 {
return 0, io.EOF
}
return rr.resolve(p)
}
func (rr *redactReader) resolve(p []byte) (int, error) {
for {
if rr.out.Len() > 0 {
// There's data in the output buffer, so let them have it all
n, err := rr.out.Read(p)
if err != nil && !errors.Is(err, io.EOF) {
return n, err //nolint:wrapcheck
}
return n, nil
}
// Read data into the buffer from the underlying reader, up to the length of the caller's
// buffer or 2x the length of the max match, which ever is greater
_, err := io.CopyN(rr.buf, rr.r, max(int64(len(p)), 2*rr.maxLen))
if err != nil {
if !errors.Is(err, io.EOF) {
return 0, err //nolint:wrapcheck
}
rr.isEOF = true
}
if err := rr.redactToBuffer(); err != nil {
return 0, err
}
if rr.isEOF {
if rr.buf.Len() > 0 {
// Copy trailing data from buffer to output
_, _ = io.Copy(rr.out, rr.buf)
}
if rr.out.Len() == 0 {
// Nothing left to do
return 0, io.EOF
}
}
}
}
func (rr *redactReader) redactToBuffer() error { //nolint:cyclop
if rr.isEOF && rr.buf.Len() == 0 {
return nil
}
var err error
// Find all matches
var matches []matchInfo
firstPartial := -1
for _, pattern := range rr.matches {
indexes, partial := byteslice.IndexAllPartial(rr.buf.Bytes(), pattern)
for _, index := range indexes {
matches = append(matches, matchInfo{start: index, end: index + len(pattern)})
}
if partial != -1 && (firstPartial == -1 || partial < firstPartial) {
firstPartial = partial
}
}
if len(matches) == 0 {
if firstPartial == -1 {
// no matches, no partial, copy it all
_, err = io.Copy(rr.out, rr.buf)
} else if firstPartial > 0 {
// Leave partial match in buffer
_, err = io.CopyN(rr.out, rr.buf, int64(firstPartial))
}
return err //nolint:wrapcheck
}
// Sort matches by start index
sort.Slice(matches, func(i, j int) bool {
return matches[i].start < matches[j].start
})
// Redact matches
lastEnd := 0
for _, match := range matches {
if match.start < lastEnd {
continue
}
// Copy bytes before the match
if match.start > lastEnd {
_, err = io.CopyN(rr.out, rr.buf, int64(match.start-lastEnd))
}
// Redact the match
rr.out.Write(rr.mask)
// discard the match
rr.buf.Next(match.end - match.start)
lastEnd = match.end
}
if firstPartial != -1 {
// Leave partial match in buffer
if len(matches) > 0 && lastEnd < firstPartial {
_, err = io.CopyN(rr.out, rr.buf, int64(firstPartial-lastEnd))
}
} else if rr.buf.Len() > 0 && firstPartial == -1 {
// No partial match, copy all of the buffer to the output
_, err = io.Copy(rr.out, rr.buf)
}
return err //nolint:wrapcheck
}