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  }