mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-04-15 07:16:34 +00:00
* fix: prevent redactors from corrupting binary files (#102) Redactors were adding newlines to files without them, corrupting binary files during support bundle collection (51 bytes → 53 bytes). Created LineReader to track original newline state and only restore newlines when they were present in the original file. - Added pkg/redact/line_reader.go - Refactored single_line.go, multi_line.go, literal.go - Added 48 tests, all passing - Verified: binary files now preserved byte-for-byte Fixes #102 * fix: handle empty lines correctly in MultiLineRedactor - Check line1 == nil instead of len(line1) == 0 for empty file detection - Fixes edge case where file containing only '\n' would be dropped - Addresses bugbot finding about empty line handling * fix: handle empty lines correctly in MultiLineRedactor - Check line1 != nil instead of len(line1) > 0 in both locations - Fixes edge case where empty trailing lines would be dropped - Fix test isolation in literal_test.go (move ResetRedactionList to parent) - Addresses bugbot findings about empty line handling * fmt * chore: update regression baselines from run 20107431959 * adding defense * fix: propagate non-EOF errors in all early return paths Ensure non-EOF errors (like buffer overflow) are properly propagated to caller in both pre-loop early returns. Addresses bugbot finding. * fix: use unique test names to prevent redaction list pollution Use t.Name() instead of hardcoded 'test' to ensure each test has unique redactor name, preventing parallel test interference --------- Co-authored-by: hedge-sparrow <sparrow@spooky.academy>
652 lines
20 KiB
Go
652 lines
20 KiB
Go
package redact
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNewSingleLineRedactor(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
re string
|
|
scan string
|
|
inputString string
|
|
wantString string
|
|
wantRedactions RedactionList
|
|
}{
|
|
{
|
|
name: "copied from default redactors",
|
|
re: `(?i)(Pwd *= *)(?P<mask>[^\;]+)(;)`,
|
|
inputString: `pwd = abcdef;`,
|
|
wantString: "pwd = ***HIDDEN***;", // No trailing newline in input, so none in output
|
|
wantRedactions: RedactionList{
|
|
ByRedactor: map[string][]Redaction{
|
|
"copied from default redactors": []Redaction{
|
|
{
|
|
RedactorName: "copied from default redactors",
|
|
CharactersRemoved: -6,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
ByFile: map[string][]Redaction{
|
|
"testfile": []Redaction{
|
|
{
|
|
RedactorName: "copied from default redactors",
|
|
CharactersRemoved: -6,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "no leading matching group", // this is not the ideal behavior - why are we dropping ungrouped match components?
|
|
re: `(?i)Pwd *= *(?P<mask>[^\;]+)(;)`,
|
|
inputString: `pwd = abcdef;`,
|
|
wantString: "***HIDDEN***;", // No trailing newline in input, so none in output
|
|
wantRedactions: RedactionList{
|
|
ByRedactor: map[string][]Redaction{
|
|
"no leading matching group": []Redaction{
|
|
{
|
|
RedactorName: "no leading matching group",
|
|
CharactersRemoved: 0,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
ByFile: map[string][]Redaction{
|
|
"testfile": []Redaction{
|
|
{
|
|
RedactorName: "no leading matching group",
|
|
CharactersRemoved: 0,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "multiple matching literals",
|
|
re: `(?i)(Pwd *= *)(?P<mask>[^\;]+)(;)`,
|
|
inputString: `pwd = abcdef;abcdef`,
|
|
wantString: "pwd = ***HIDDEN***;abcdef", // No trailing newline in input, so none in output
|
|
wantRedactions: RedactionList{
|
|
ByRedactor: map[string][]Redaction{
|
|
"multiple matching literals": []Redaction{
|
|
{
|
|
RedactorName: "multiple matching literals",
|
|
CharactersRemoved: -6,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
ByFile: map[string][]Redaction{
|
|
"testfile": []Redaction{
|
|
{
|
|
RedactorName: "multiple matching literals",
|
|
CharactersRemoved: -6,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Redact values for environment variables that look like AWS Secret Access Keys",
|
|
re: `(?i)("name":"[^\"]*SECRET_?ACCESS_?KEY","value":")(?P<mask>[^\"]*)(")`,
|
|
inputString: `{"name":"SECRET_ACCESS_KEY","value":"123"}`,
|
|
wantString: `{"name":"SECRET_ACCESS_KEY","value":"***HIDDEN***"}`, // No trailing newline in input, so none in output
|
|
wantRedactions: RedactionList{
|
|
ByRedactor: map[string][]Redaction{
|
|
"Redact values for environment variables that look like AWS Secret Access Keys": []Redaction{
|
|
{
|
|
RedactorName: "Redact values for environment variables that look like AWS Secret Access Keys",
|
|
CharactersRemoved: -9,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
ByFile: map[string][]Redaction{
|
|
"testfile": []Redaction{
|
|
{
|
|
RedactorName: "Redact values for environment variables that look like AWS Secret Access Keys",
|
|
CharactersRemoved: -9,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Redact connection strings with username and password",
|
|
re: `(?i)(https?|ftp)(:\/\/)(?P<mask>[^:\"\/]+){1}(:)(?P<mask>[^@\"\/]+){1}(?P<host>@[^:\/\s\"]+){1}(?P<port>:[\d]+)?`,
|
|
inputString: `http://user:password@host:8888`,
|
|
wantString: "http://***HIDDEN***:***HIDDEN***@host:8888", // No trailing newline in input, so none in output
|
|
wantRedactions: RedactionList{
|
|
ByRedactor: map[string][]Redaction{
|
|
"Redact connection strings with username and password": []Redaction{
|
|
{
|
|
RedactorName: "Redact connection strings with username and password",
|
|
CharactersRemoved: -12,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
ByFile: map[string][]Redaction{
|
|
"testfile": []Redaction{
|
|
{
|
|
RedactorName: "Redact connection strings with username and password",
|
|
CharactersRemoved: -12,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Redact values for environment variables that look like AWS Secret Access Keys With Scan",
|
|
re: `(?i)("name":"[^\"]*SECRET_?ACCESS_?KEY","value":")(?P<mask>[^\"]*)(")`,
|
|
scan: `secret_?access_?key`,
|
|
inputString: `{"name":"SECRET_ACCESS_KEY","value":"123"}`,
|
|
wantString: `{"name":"SECRET_ACCESS_KEY","value":"***HIDDEN***"}`, // No trailing newline in input, so none in output
|
|
wantRedactions: RedactionList{
|
|
ByRedactor: map[string][]Redaction{
|
|
"Redact values for environment variables that look like AWS Secret Access Keys With Scan": {
|
|
{
|
|
RedactorName: "Redact values for environment variables that look like AWS Secret Access Keys With Scan",
|
|
CharactersRemoved: -9,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
ByFile: map[string][]Redaction{
|
|
"testfile": {
|
|
{
|
|
RedactorName: "Redact values for environment variables that look like AWS Secret Access Keys With Scan",
|
|
CharactersRemoved: -9,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Redact values for environment variables that look like Access Keys ID With Scan",
|
|
re: `(?i)("name":"[^\"]*ACCESS_?KEY_?ID","value":")(?P<mask>[^\"]*)(")`,
|
|
scan: `access_?key_?id`,
|
|
inputString: `{"name":"ACCESS_KEY_ID","value":"123"}`,
|
|
wantString: `{"name":"ACCESS_KEY_ID","value":"***HIDDEN***"}`, // No trailing newline in input, so none in output
|
|
wantRedactions: RedactionList{
|
|
ByRedactor: map[string][]Redaction{
|
|
"Redact values for environment variables that look like Access Keys ID With Scan": {
|
|
{
|
|
RedactorName: "Redact values for environment variables that look like Access Keys ID With Scan",
|
|
CharactersRemoved: -9,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
ByFile: map[string][]Redaction{
|
|
"testfile": {
|
|
{
|
|
RedactorName: "Redact values for environment variables that look like Access Keys ID With Scan",
|
|
CharactersRemoved: -9,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Redact values for environment variables that look like Owner Account With Scan",
|
|
re: `(?i)("name":"[^\"]*OWNER_?ACCOUNT","value":")(?P<mask>[^\"]*)(")`,
|
|
scan: `owner_?account`,
|
|
inputString: `{"name":"OWNER_ACCOUNT","value":"123"}`,
|
|
wantString: `{"name":"OWNER_ACCOUNT","value":"***HIDDEN***"}`, // No trailing newline in input, so none in output
|
|
wantRedactions: RedactionList{
|
|
ByRedactor: map[string][]Redaction{
|
|
"Redact values for environment variables that look like Owner Account With Scan": {
|
|
{
|
|
RedactorName: "Redact values for environment variables that look like Owner Account With Scan",
|
|
CharactersRemoved: -9,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
ByFile: map[string][]Redaction{
|
|
"testfile": {
|
|
{
|
|
RedactorName: "Redact values for environment variables that look like Owner Account With Scan",
|
|
CharactersRemoved: -9,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Redact 'Data Source' values With Scan",
|
|
re: `(?i)(Data Source *= *)(?P<mask>[^\;]+)(;)`,
|
|
scan: `data source`,
|
|
inputString: `Data Source = abcdef;`,
|
|
wantString: `Data Source = ***HIDDEN***;`, // No trailing newline in input, so none in output
|
|
wantRedactions: RedactionList{
|
|
ByRedactor: map[string][]Redaction{
|
|
"Redact 'Data Source' values With Scan": {
|
|
{
|
|
RedactorName: "Redact 'Data Source' values With Scan",
|
|
CharactersRemoved: -6,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
ByFile: map[string][]Redaction{
|
|
"testfile": {
|
|
{
|
|
RedactorName: "Redact 'Data Source' values With Scan",
|
|
CharactersRemoved: -6,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Redact connection strings With Scan",
|
|
re: `(?i)(https?|ftp)(:\/\/)(?P<mask>[^:\"\/]+){1}(:)(?P<mask>[^@\"\/]+){1}(?P<host>@[^:\/\s\"]+){1}(?P<port>:[\d]+)?`,
|
|
scan: `https?|ftp`,
|
|
inputString: `http://user:password@host:8888;`,
|
|
wantString: `http://***HIDDEN***:***HIDDEN***@host:8888;`, // No trailing newline in input, so none in output
|
|
wantRedactions: RedactionList{
|
|
ByRedactor: map[string][]Redaction{
|
|
"Redact connection strings With Scan": {
|
|
{
|
|
RedactorName: "Redact connection strings With Scan",
|
|
CharactersRemoved: -12,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
ByFile: map[string][]Redaction{
|
|
"testfile": {
|
|
{
|
|
RedactorName: "Redact connection strings With Scan",
|
|
CharactersRemoved: -12,
|
|
Line: 1,
|
|
File: "testfile",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Multiple newlines with no match",
|
|
re: `abcd`,
|
|
inputString: "no match\n\n no match \n\n",
|
|
wantString: "no match\n\n no match \n\n",
|
|
wantRedactions: RedactionList{
|
|
ByRedactor: map[string][]Redaction{},
|
|
ByFile: map[string][]Redaction{},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
req := require.New(t)
|
|
ResetRedactionList()
|
|
reRunner, err := NewSingleLineRedactor(LineRedactor{
|
|
regex: tt.re,
|
|
scan: tt.scan,
|
|
}, MASK_TEXT, "testfile", tt.name, false)
|
|
req.NoError(err)
|
|
|
|
outReader := reRunner.Redact(bytes.NewReader([]byte(tt.inputString)), "")
|
|
gotBytes, err := io.ReadAll(outReader)
|
|
req.NoError(err)
|
|
req.Equal(tt.wantString, string(gotBytes))
|
|
|
|
actualRedactions := GetRedactionList()
|
|
ResetRedactionList()
|
|
req.Equal(tt.wantRedactions, actualRedactions)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test 2.15: Binary file (no newlines) → unchanged
|
|
func TestSingleLineRedactor_BinaryFile(t *testing.T) {
|
|
ResetRedactionList()
|
|
defer ResetRedactionList()
|
|
|
|
// Binary content with no newlines
|
|
binaryData := []byte{0x01, 0x02, 0x03, 0x04, 0x00, 0xFF, 0xFE, 0xAB, 0xCD}
|
|
|
|
redactor, err := NewSingleLineRedactor(
|
|
LineRedactor{regex: "password"}, // Pattern that won't match binary
|
|
MASK_TEXT, "testfile", "test", false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
out := redactor.Redact(bytes.NewReader(binaryData), "test.bin")
|
|
result, err := io.ReadAll(out)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, binaryData, result, "Binary file should be unchanged")
|
|
}
|
|
|
|
// Test: Binary file with every single byte value (0x00 -> 0xFF)
|
|
func TestSingleLineRedactor_AllSingleByteValues(t *testing.T) {
|
|
ResetRedactionList()
|
|
defer ResetRedactionList()
|
|
|
|
// Create binary data with every possible byte value
|
|
binaryData := make([]byte, 256)
|
|
for i := 0; i < 256; i++ {
|
|
binaryData[i] = byte(i)
|
|
}
|
|
|
|
redactor, err := NewSingleLineRedactor(
|
|
LineRedactor{regex: `secret`},
|
|
MASK_TEXT, "testfile", t.Name(), false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
out := redactor.Redact(bytes.NewReader(binaryData), "test.bin")
|
|
result, err := io.ReadAll(out)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, binaryData, result, "Binary file with all byte values should be unchanged")
|
|
require.Len(t, result, 256, "Should preserve all 256 bytes")
|
|
}
|
|
|
|
// Test: Binary file with every two-byte combination (0x00+0x00 -> 0xFF+0xFF)
|
|
func TestSingleLineRedactor_AllTwoByteValues(t *testing.T) {
|
|
ResetRedactionList()
|
|
defer ResetRedactionList()
|
|
|
|
// Create binary data with all 65,536 two-byte combinations (128KB)
|
|
binaryData := make([]byte, 256*256*2)
|
|
pos := 0
|
|
for i := 0; i < 256; i++ {
|
|
for j := 0; j < 256; j++ {
|
|
binaryData[pos] = byte(i)
|
|
binaryData[pos+1] = byte(j)
|
|
pos += 2
|
|
}
|
|
}
|
|
|
|
redactor, err := NewSingleLineRedactor(
|
|
LineRedactor{regex: `secret`},
|
|
MASK_TEXT, "testfile", t.Name(), false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
out := redactor.Redact(bytes.NewReader(binaryData), "test.bin")
|
|
result, err := io.ReadAll(out)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, binaryData, result, "Binary file with all two-byte combinations should be unchanged")
|
|
require.Len(t, result, 256*256*2, "Should preserve all 131,072 bytes (64k combinations)")
|
|
}
|
|
|
|
// Test 2.16: Text file with \n → preserved
|
|
func TestSingleLineRedactor_TextFileWithNewline(t *testing.T) {
|
|
ResetRedactionList()
|
|
defer ResetRedactionList()
|
|
|
|
input := "hello world\n"
|
|
|
|
redactor, err := NewSingleLineRedactor(
|
|
LineRedactor{regex: "xyz"}, // No match
|
|
MASK_TEXT, "testfile", "test", false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
out := redactor.Redact(bytes.NewReader([]byte(input)), "test.txt")
|
|
result, err := io.ReadAll(out)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, "hello world\n", string(result), "Trailing newline should be preserved")
|
|
}
|
|
|
|
// Test 2.17: Text file without \n → preserved
|
|
func TestSingleLineRedactor_TextFileWithoutNewline(t *testing.T) {
|
|
ResetRedactionList()
|
|
defer ResetRedactionList()
|
|
|
|
input := "hello world"
|
|
|
|
redactor, err := NewSingleLineRedactor(
|
|
LineRedactor{regex: "xyz"}, // No match
|
|
MASK_TEXT, "testfile", "test", false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
out := redactor.Redact(bytes.NewReader([]byte(input)), "test.txt")
|
|
result, err := io.ReadAll(out)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, "hello world", string(result), "No newline should be added")
|
|
}
|
|
|
|
// Test 2.18: Empty file → unchanged
|
|
func TestSingleLineRedactor_EmptyFile(t *testing.T) {
|
|
ResetRedactionList()
|
|
defer ResetRedactionList()
|
|
|
|
input := ""
|
|
|
|
redactor, err := NewSingleLineRedactor(
|
|
LineRedactor{regex: "password"},
|
|
MASK_TEXT, "testfile", "test", false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
out := redactor.Redact(bytes.NewReader([]byte(input)), "test.txt")
|
|
result, err := io.ReadAll(out)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, "", string(result), "Empty file should remain empty")
|
|
}
|
|
|
|
// Test 2.19: Single line with secret → redacted correctly
|
|
func TestSingleLineRedactor_SingleLineWithSecret(t *testing.T) {
|
|
ResetRedactionList()
|
|
defer ResetRedactionList()
|
|
|
|
input := "password=secret123"
|
|
|
|
redactor, err := NewSingleLineRedactor(
|
|
LineRedactor{regex: `(?i)(password=)(?P<mask>.*)`},
|
|
MASK_TEXT, "testfile", "test", false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
out := redactor.Redact(bytes.NewReader([]byte(input)), "test.txt")
|
|
result, err := io.ReadAll(out)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, "password=***HIDDEN***", string(result))
|
|
}
|
|
|
|
// Test 2.20: Multiple lines with secrets → all redacted
|
|
func TestSingleLineRedactor_MultipleLinesWithSecrets(t *testing.T) {
|
|
ResetRedactionList()
|
|
defer ResetRedactionList()
|
|
|
|
input := "password=secret1\npassword=secret2\npassword=secret3\n"
|
|
|
|
redactor, err := NewSingleLineRedactor(
|
|
LineRedactor{regex: `(?i)(password=)(?P<mask>.*)`},
|
|
MASK_TEXT, "testfile", "test", false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
out := redactor.Redact(bytes.NewReader([]byte(input)), "test.txt")
|
|
result, err := io.ReadAll(out)
|
|
|
|
require.NoError(t, err)
|
|
expected := "password=***HIDDEN***\npassword=***HIDDEN***\npassword=***HIDDEN***\n"
|
|
require.Equal(t, expected, string(result))
|
|
}
|
|
|
|
// Test 2.21: Scan pattern filters correctly
|
|
func TestSingleLineRedactor_ScanPatternFilters(t *testing.T) {
|
|
ResetRedactionList()
|
|
defer ResetRedactionList()
|
|
|
|
input := "password=secret\nusername=admin\n"
|
|
|
|
redactor, err := NewSingleLineRedactor(
|
|
LineRedactor{
|
|
regex: `(?i)(password=)(?P<mask>.*)`,
|
|
scan: `password`, // Only process lines containing "password"
|
|
},
|
|
MASK_TEXT, "testfile", "test", false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
out := redactor.Redact(bytes.NewReader([]byte(input)), "test.txt")
|
|
result, err := io.ReadAll(out)
|
|
|
|
require.NoError(t, err)
|
|
expected := "password=***HIDDEN***\nusername=admin\n"
|
|
require.Equal(t, expected, string(result))
|
|
}
|
|
|
|
// Test 2.22: File with only one newline \n → one newline out
|
|
func TestSingleLineRedactor_OnlyNewline(t *testing.T) {
|
|
ResetRedactionList()
|
|
defer ResetRedactionList()
|
|
|
|
input := "\n"
|
|
|
|
redactor, err := NewSingleLineRedactor(
|
|
LineRedactor{regex: "password"},
|
|
MASK_TEXT, "testfile", "test", false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
out := redactor.Redact(bytes.NewReader([]byte(input)), "test.txt")
|
|
result, err := io.ReadAll(out)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, "\n", string(result))
|
|
}
|
|
|
|
// Test 2.23: Mixed binary/text content
|
|
func TestSingleLineRedactor_MixedContent(t *testing.T) {
|
|
ResetRedactionList()
|
|
defer ResetRedactionList()
|
|
|
|
// Binary data with embedded newline
|
|
input := []byte{0x01, 0x02, '\n', 0x03, 0x04}
|
|
|
|
redactor, err := NewSingleLineRedactor(
|
|
LineRedactor{regex: "password"},
|
|
MASK_TEXT, "testfile", "test", false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
out := redactor.Redact(bytes.NewReader(input), "test.bin")
|
|
result, err := io.ReadAll(out)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, input, result, "Mixed content should be preserved")
|
|
}
|
|
|
|
// Test: Windows CRLF (\r\n) line endings preserved
|
|
func TestSingleLineRedactor_WindowsLineEndings(t *testing.T) {
|
|
ResetRedactionList()
|
|
defer ResetRedactionList()
|
|
|
|
// Windows-style line endings with no secrets
|
|
input := "line1\r\nline2\r\nline3\r\n"
|
|
|
|
redactor, err := NewSingleLineRedactor(
|
|
LineRedactor{regex: `secret`},
|
|
MASK_TEXT, "testfile", t.Name(), false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
out := redactor.Redact(bytes.NewReader([]byte(input)), "test.txt")
|
|
result, err := io.ReadAll(out)
|
|
|
|
require.NoError(t, err)
|
|
// Windows line endings should be preserved exactly
|
|
require.Equal(t, "line1\r\nline2\r\nline3\r\n", string(result),
|
|
"Windows CRLF line endings should be preserved, not converted to LF")
|
|
}
|
|
|
|
// Test: Windows CRLF with redaction
|
|
func TestSingleLineRedactor_WindowsLineEndingsWithRedaction(t *testing.T) {
|
|
ResetRedactionList()
|
|
defer ResetRedactionList()
|
|
|
|
// Windows-style line endings with a secret
|
|
// Note: LineReader splits on \n, so line content includes \r
|
|
input := "password=secret123\r\nusername=admin\r\n"
|
|
|
|
// Use a regex that doesn't capture the \r
|
|
redactor, err := NewSingleLineRedactor(
|
|
LineRedactor{regex: `(password=)(?P<mask>[^\r\n]+)`},
|
|
MASK_TEXT, "testfile", t.Name(), false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
out := redactor.Redact(bytes.NewReader([]byte(input)), "test.txt")
|
|
result, err := io.ReadAll(out)
|
|
|
|
require.NoError(t, err)
|
|
// \r\n should be preserved: regex doesn't capture \r, so it stays in output
|
|
expected := "password=***HIDDEN***\r\nusername=admin\r\n"
|
|
require.Equal(t, expected, string(result),
|
|
"Windows CRLF should be preserved - \\r not captured by regex, \\n added by LineReader")
|
|
}
|
|
|
|
// Test 2.24: Large binary file (1MB, no newlines) → preserved
|
|
func TestSingleLineRedactor_LargeBinaryFile(t *testing.T) {
|
|
ResetRedactionList()
|
|
defer ResetRedactionList()
|
|
|
|
// Create 1MB of binary data
|
|
largeData := make([]byte, 1024*1024)
|
|
for i := range largeData {
|
|
largeData[i] = byte(i % 256)
|
|
}
|
|
|
|
redactor, err := NewSingleLineRedactor(
|
|
LineRedactor{regex: "password"},
|
|
MASK_TEXT, "testfile", "test", false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
out := redactor.Redact(bytes.NewReader(largeData), "large.bin")
|
|
result, err := io.ReadAll(out)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, largeData, result, "Large binary file should be unchanged")
|
|
}
|