github.com/Comcast/plax@v0.8.32/dsl/redact.go (about) 1 package dsl 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 "sync" 8 ) 9 10 // WantsRedaction reports whether the parameter's value should be 11 // redacted. 12 // 13 // Currently if a parameter starts with "X_" after ignoring special 14 // characters, then the parameter's value should be redacted. 15 func WantsRedaction(p string) bool { 16 return strings.HasPrefix(strings.Trim(p, "?!*"), "X_") 17 } 18 19 // Redact might replace part of s with <redacted> depending on the 20 // given Regexp. 21 // 22 // If the Regexp has no groups, all substrings that match the Regexp 23 // are redacted. 24 // 25 // For each named group with a name starting with "redact", that group 26 // is redacted (for all matches). 27 // 28 // If there are groups but none has a name starting with "redact", 29 // then the first matching (non-captured) group is redacted. 30 func Redact(r *regexp.Regexp, s string) string { 31 replacement := "<redacted>" 32 if r.NumSubexp() == 0 { 33 return r.ReplaceAllString(s, replacement) 34 } 35 36 var acc string 37 for { 38 match := r.FindStringSubmatchIndex(s) 39 if match == nil { 40 acc += s 41 break 42 } 43 var ( 44 redacted = false 45 names = r.SubexpNames() 46 last = match[1] 47 start, end int 48 ) 49 for i, name := range names { 50 // First one is anonymous everything group. 51 if strings.HasPrefix(name, "redact") { 52 redacted = true 53 start, end = match[2*i], match[2*i+1] 54 break 55 } 56 } 57 58 if !redacted { 59 // The first group will be redacted. 60 start, end = match[2], match[3] 61 } 62 63 acc += s[0:start] + replacement + s[end:last] 64 65 s = s[last:] 66 } 67 68 return acc 69 } 70 71 // Redactions is set of patterns that can be redacted by the Redactf 72 // method. 73 type Redactions struct { 74 // Redact enables or disables redactions. 75 // 76 // The sketchy field name is for backwards compatibility. 77 Redact bool 78 79 // Pattens maps strings representing regular expressions to 80 // Repexps. 81 Patterns map[string]*regexp.Regexp 82 83 // RWMutex makes this gear safe for concurrent use. 84 sync.RWMutex 85 } 86 87 // NewRedactions makes a disabled Redactions. 88 func NewRedactions() *Redactions { 89 return &Redactions{ 90 Patterns: make(map[string]*regexp.Regexp), 91 } 92 } 93 94 // Add compiles the given string as a regular expression and installs 95 // that regexp as a desired redaction. 96 func (r *Redactions) Add(pat string) error { 97 if len(strings.TrimSpace(pat)) == 0 { 98 // We'll ignore degenerate patterns that (presumably) 99 // unintentional. Example: An X_ parameter has an 100 // empty string as its value. That'd result in trying 101 // to redact ever zero-length string, which is not 102 // helpful. 103 return nil 104 } 105 p, err := regexp.Compile(pat) 106 if err == nil { 107 r.Lock() 108 r.Patterns[pat] = p 109 r.Unlock() 110 } 111 return err 112 } 113 114 // Redactf calls fmt.Sprintf and then redacts the result. 115 func (r *Redactions) Redactf(format string, args ...interface{}) string { 116 s := fmt.Sprintf(format, args...) 117 if !r.Redact { 118 return s 119 } 120 r.RLock() 121 for _, p := range r.Patterns { 122 s = Redact(p, s) 123 } 124 r.RUnlock() 125 return s 126 } 127 128 // AddRedaction compiles the given string as a regular expression and 129 // installs that regexp as a desired redaction in logging output. 130 func (c *Ctx) AddRedaction(pat string) error { 131 return c.Redactions.Add(pat) 132 } 133 134 // Redactf calls c.Printf with any requested redactions. 135 func (c *Ctx) Redactf(format string, args ...interface{}) { 136 c.Printf("%s", c.Redactions.Redactf(format, args...)) 137 }