mirror of
https://code.forgejo.org/forgejo/runner.git
synced 2025-07-27 17:28:35 +00:00
- the longest secret is masked first - multiline secrets are masked before single line secrets - O(multiline * log rows) to not degrade performances when there are no multiline secrets
146 lines
3.4 KiB
Go
146 lines
3.4 KiB
Go
// Copyright 2025 The Forgejo Authors.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package report
|
|
|
|
import (
|
|
"cmp"
|
|
"slices"
|
|
"strings"
|
|
|
|
runnerv1 "code.forgejo.org/forgejo/actions-proto/runner/v1"
|
|
)
|
|
|
|
type masker struct {
|
|
replacer *strings.Replacer
|
|
lines []string
|
|
multiLines [][]string
|
|
}
|
|
|
|
func newMasker() *masker {
|
|
return &masker{
|
|
lines: make([]string, 0, 10),
|
|
multiLines: make([][]string, 0, 10),
|
|
}
|
|
}
|
|
|
|
func (o *masker) add(secret string) {
|
|
if len(secret) == 0 {
|
|
return
|
|
}
|
|
o.replacer = nil
|
|
lines := strings.Split(strings.ReplaceAll(secret, "\r\n", "\n"), "\n")
|
|
if len(lines) > 1 {
|
|
o.multiLines = append(o.multiLines, lines)
|
|
// make sure the longest secret are replaced first
|
|
slices.SortFunc(o.multiLines, func(a, b []string) int {
|
|
return cmp.Compare(len(b), len(a))
|
|
})
|
|
} else {
|
|
o.lines = append(o.lines, lines[0])
|
|
// make sure the longest secret are replaced first
|
|
slices.SortFunc(o.lines, func(a, b string) int {
|
|
return cmp.Compare(len(b), len(a))
|
|
})
|
|
}
|
|
}
|
|
|
|
func (o *masker) getReplacer() *strings.Replacer {
|
|
if o.replacer == nil {
|
|
oldnew := make([]string, 0, len(o.lines)*2)
|
|
for _, line := range o.lines {
|
|
oldnew = append(oldnew, line, "***")
|
|
}
|
|
o.replacer = strings.NewReplacer(oldnew...)
|
|
}
|
|
return o.replacer
|
|
}
|
|
|
|
func (o *masker) replaceLines(rows []*runnerv1.LogRow) {
|
|
r := o.getReplacer()
|
|
for _, row := range rows {
|
|
row.Content = r.Replace(row.Content)
|
|
}
|
|
}
|
|
|
|
func (o *masker) maybeReplaceMultiline(multiLine []string, rows []*runnerv1.LogRow) bool {
|
|
equal, needMore := o.equalMultiline(multiLine, rows)
|
|
if needMore {
|
|
return needMore
|
|
}
|
|
if equal {
|
|
o.replaceMultiline(multiLine, rows)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (o *masker) trimEOL(s string) string {
|
|
return strings.TrimRightFunc(s, func(r rune) bool { return r == '\r' || r == '\n' })
|
|
}
|
|
|
|
func (o *masker) equalMultiline(multiLine []string, rows []*runnerv1.LogRow) (equal, needMore bool) {
|
|
if len(rows) < 2 {
|
|
needMore = true
|
|
return
|
|
}
|
|
|
|
lastIndex := len(multiLine) - 1
|
|
first := multiLine[0]
|
|
if !strings.HasSuffix(o.trimEOL(rows[0].Content), first) {
|
|
return // unreachable because the caller checks that already
|
|
}
|
|
for i, line := range multiLine[1:lastIndex] {
|
|
rowIndex := i + 1
|
|
if rowIndex >= len(rows) {
|
|
needMore = true
|
|
return
|
|
}
|
|
if o.trimEOL(rows[rowIndex].Content) != line {
|
|
return
|
|
}
|
|
}
|
|
last := multiLine[lastIndex]
|
|
if lastIndex >= len(rows) {
|
|
needMore = true
|
|
return
|
|
}
|
|
if !strings.HasPrefix(o.trimEOL(rows[lastIndex].Content), last) {
|
|
return
|
|
}
|
|
equal = true
|
|
return
|
|
}
|
|
|
|
func (o *masker) replaceMultiline(multiLine []string, rows []*runnerv1.LogRow) {
|
|
lastIndex := len(multiLine) - 1
|
|
first := multiLine[0]
|
|
rows[0].Content = strings.TrimSuffix(rows[0].Content, first) + "***"
|
|
for _, row := range rows[1:lastIndex] {
|
|
row.Content = "***"
|
|
}
|
|
last := multiLine[lastIndex]
|
|
rows[lastIndex].Content = "***" + strings.TrimPrefix(rows[lastIndex].Content, last)
|
|
}
|
|
|
|
func (o *masker) replaceMultilines(rows []*runnerv1.LogRow) bool {
|
|
for _, multiLine := range o.multiLines {
|
|
for i, row := range rows {
|
|
if strings.HasSuffix(o.trimEOL(row.Content), multiLine[0]) {
|
|
needMore := o.maybeReplaceMultiline(multiLine, rows[i:])
|
|
if needMore {
|
|
return needMore
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (o *masker) replace(rows []*runnerv1.LogRow, noMore bool) bool {
|
|
needMore := o.replaceMultilines(rows)
|
|
if !noMore && needMore {
|
|
return needMore
|
|
}
|
|
o.replaceLines(rows)
|
|
return false
|
|
}
|