-
Notifications
You must be signed in to change notification settings - Fork 14
/
redact.go
137 lines (121 loc) · 3.24 KB
/
redact.go
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
package dsl
import (
"fmt"
"regexp"
"strings"
"sync"
)
// WantsRedaction reports whether the parameter's value should be
// redacted.
//
// Currently if a parameter starts with "X_" after ignoring special
// characters, then the parameter's value should be redacted.
func WantsRedaction(p string) bool {
return strings.HasPrefix(strings.Trim(p, "?!*"), "X_")
}
// Redact might replace part of s with <redacted> depending on the
// given Regexp.
//
// If the Regexp has no groups, all substrings that match the Regexp
// are redacted.
//
// For each named group with a name starting with "redact", that group
// is redacted (for all matches).
//
// If there are groups but none has a name starting with "redact",
// then the first matching (non-captured) group is redacted.
func Redact(r *regexp.Regexp, s string) string {
replacement := "<redacted>"
if r.NumSubexp() == 0 {
return r.ReplaceAllString(s, replacement)
}
var acc string
for {
match := r.FindStringSubmatchIndex(s)
if match == nil {
acc += s
break
}
var (
redacted = false
names = r.SubexpNames()
last = match[1]
start, end int
)
for i, name := range names {
// First one is anonymous everything group.
if strings.HasPrefix(name, "redact") {
redacted = true
start, end = match[2*i], match[2*i+1]
break
}
}
if !redacted {
// The first group will be redacted.
start, end = match[2], match[3]
}
acc += s[0:start] + replacement + s[end:last]
s = s[last:]
}
return acc
}
// Redactions is set of patterns that can be redacted by the Redactf
// method.
type Redactions struct {
// Redact enables or disables redactions.
//
// The sketchy field name is for backwards compatibility.
Redact bool
// Pattens maps strings representing regular expressions to
// Repexps.
Patterns map[string]*regexp.Regexp
// RWMutex makes this gear safe for concurrent use.
sync.RWMutex
}
// NewRedactions makes a disabled Redactions.
func NewRedactions() *Redactions {
return &Redactions{
Patterns: make(map[string]*regexp.Regexp),
}
}
// Add compiles the given string as a regular expression and installs
// that regexp as a desired redaction.
func (r *Redactions) Add(pat string) error {
if len(strings.TrimSpace(pat)) == 0 {
// We'll ignore degenerate patterns that (presumably)
// unintentional. Example: An X_ parameter has an
// empty string as its value. That'd result in trying
// to redact ever zero-length string, which is not
// helpful.
return nil
}
p, err := regexp.Compile(pat)
if err == nil {
r.Lock()
r.Patterns[pat] = p
r.Unlock()
}
return err
}
// Redactf calls fmt.Sprintf and then redacts the result.
func (r *Redactions) Redactf(format string, args ...interface{}) string {
s := fmt.Sprintf(format, args...)
if !r.Redact {
return s
}
r.RLock()
for _, p := range r.Patterns {
s = Redact(p, s)
}
r.RUnlock()
return s
}
// AddRedaction compiles the given string as a regular expression and
// installs that regexp as a desired redaction in logging output.
func (c *Ctx) AddRedaction(pat string) error {
return c.Redactions.Add(pat)
}
// Redactf calls c.Printf with any requested redactions.
func (c *Ctx) Redactf(format string, args ...interface{}) {
c.Printf("%s", c.Redactions.Redactf(format, args...))
}