github.com/anchore/syft@v1.38.2/syft/format/internal/testutil/redactor.go (about)

     1  package testutil
     2  
     3  import (
     4  	"bytes"
     5  	"regexp"
     6  )
     7  
     8  var (
     9  	_ Redactor = (*RedactorFn)(nil)
    10  	_ Redactor = (*PatternReplacement)(nil)
    11  	_ Redactor = (*ValueReplacement)(nil)
    12  	_ Redactor = (*Redactions)(nil)
    13  )
    14  
    15  type Redactor interface {
    16  	Redact([]byte) []byte
    17  }
    18  
    19  // Replace by function //////////////////////////////
    20  
    21  type RedactorFn func([]byte) []byte
    22  
    23  func (r RedactorFn) Redact(b []byte) []byte {
    24  	return r(b)
    25  }
    26  
    27  // Replace by regex //////////////////////////////
    28  
    29  type PatternReplacement struct {
    30  	Search  *regexp.Regexp
    31  	Groups  []string
    32  	Replace string
    33  }
    34  
    35  func NewPatternReplacement(r *regexp.Regexp) PatternReplacement {
    36  	return PatternReplacement{
    37  		Search:  r,
    38  		Replace: "redacted",
    39  	}
    40  }
    41  
    42  func (p PatternReplacement) Redact(b []byte) []byte {
    43  	if len(p.Groups) == 0 {
    44  		return p.Search.ReplaceAll(b, []byte(p.Replace))
    45  	}
    46  
    47  	return p.redactNamedGroups(b)
    48  }
    49  
    50  func (p PatternReplacement) redactNamedGroups(b []byte) []byte {
    51  	groupsToReplace := make(map[string]bool)
    52  	for _, g := range p.Groups {
    53  		groupsToReplace[g] = true
    54  	}
    55  
    56  	subexpNames := p.Search.SubexpNames()
    57  
    58  	return p.Search.ReplaceAllFunc(b, func(match []byte) []byte {
    59  		indexes := p.Search.FindSubmatchIndex(match)
    60  		if indexes == nil {
    61  			return match
    62  		}
    63  
    64  		result := make([]byte, len(match))
    65  		copy(result, match)
    66  
    67  		// keep track of the offset as we replace groups
    68  		offset := 0
    69  
    70  		// process each named group
    71  		for i, name := range subexpNames {
    72  			// skip the full match (i==0) and groups we don't want to replace
    73  			if i == 0 || !groupsToReplace[name] {
    74  				continue
    75  			}
    76  
    77  			// get the start and end positions of this group
    78  			startPos := indexes[2*i]
    79  			endPos := indexes[2*i+1]
    80  
    81  			// skip if the group didn't match
    82  			if startPos < 0 || endPos < 0 {
    83  				continue
    84  			}
    85  
    86  			// adjust positions based on previous replacements
    87  			startPos += offset
    88  			endPos += offset
    89  
    90  			// replace the group with our replacement text
    91  			beforeGroup := result[:startPos]
    92  			afterGroup := result[endPos:]
    93  
    94  			// calculate the new offset
    95  			oldLen := endPos - startPos
    96  			newLen := len(p.Replace)
    97  			offset += (newLen - oldLen)
    98  
    99  			result = append(beforeGroup, append([]byte(p.Replace), afterGroup...)...) //nolint:gocritic
   100  		}
   101  
   102  		return result
   103  	})
   104  }
   105  
   106  // Replace by value //////////////////////////////
   107  
   108  type ValueReplacement struct {
   109  	Search  string
   110  	Replace string
   111  }
   112  
   113  func NewValueReplacement(v string) ValueReplacement {
   114  	return ValueReplacement{
   115  		Search:  v,
   116  		Replace: "redacted",
   117  	}
   118  }
   119  
   120  func (v ValueReplacement) Redact(b []byte) []byte {
   121  	return bytes.ReplaceAll(b, []byte(v.Search), []byte(v.Replace))
   122  }
   123  
   124  // Handle a collection of redactors //////////////////////////////
   125  
   126  type Redactions struct {
   127  	redactors []Redactor
   128  }
   129  
   130  func NewRedactions(redactors ...Redactor) *Redactions {
   131  	r := &Redactions{
   132  		redactors: redactors,
   133  	}
   134  
   135  	return r.WithFunctions(carriageRedactor)
   136  }
   137  
   138  func (r *Redactions) WithPatternRedactors(values map[string]string) *Redactions {
   139  	for k, v := range values {
   140  		r.redactors = append(r.redactors,
   141  			PatternReplacement{
   142  				Search:  regexp.MustCompile(k),
   143  				Replace: v,
   144  			},
   145  		)
   146  	}
   147  	return r
   148  }
   149  
   150  func (r *Redactions) WithPatternRedactorSpec(values ...PatternReplacement) *Redactions {
   151  	for _, v := range values {
   152  		r.redactors = append(r.redactors, v)
   153  	}
   154  	return r
   155  }
   156  
   157  func (r *Redactions) WithValueRedactors(values map[string]string) *Redactions {
   158  	for k, v := range values {
   159  		r.redactors = append(r.redactors,
   160  			ValueReplacement{
   161  				Search:  k,
   162  				Replace: v,
   163  			},
   164  		)
   165  	}
   166  	return r
   167  }
   168  
   169  func (r *Redactions) WithPatternsRedacted(values ...string) *Redactions {
   170  	for _, pattern := range values {
   171  		r.redactors = append(r.redactors,
   172  			NewPatternReplacement(regexp.MustCompile(pattern)),
   173  		)
   174  	}
   175  	return r
   176  }
   177  
   178  func (r *Redactions) WithValuesRedacted(values ...string) *Redactions {
   179  	for _, v := range values {
   180  		r.redactors = append(r.redactors,
   181  			NewValueReplacement(v),
   182  		)
   183  	}
   184  	return r
   185  }
   186  
   187  func (r *Redactions) WithFunctions(values ...func([]byte) []byte) *Redactions {
   188  	for _, fn := range values {
   189  		r.redactors = append(r.redactors,
   190  			RedactorFn(fn),
   191  		)
   192  	}
   193  	return r
   194  }
   195  
   196  func (r *Redactions) WithRedactors(rs ...Redactor) *Redactions {
   197  	r.redactors = append(r.redactors, rs...)
   198  	return r
   199  }
   200  
   201  func (r Redactions) Redact(b []byte) []byte {
   202  	for _, redactor := range r.redactors {
   203  		b = redactor.Redact(b)
   204  	}
   205  	return b
   206  }
   207  
   208  func carriageRedactor(s []byte) []byte {
   209  	return bytes.ReplaceAll(s, []byte("\r\n"), []byte("\n"))
   210  }