diff --git a/pkg/safecsv/cell.go b/pkg/safecsv/cell.go index 78828766c..0be60d0b7 100644 --- a/pkg/safecsv/cell.go +++ b/pkg/safecsv/cell.go @@ -41,13 +41,18 @@ func SanitizeRecord(record []string) []string { } // SanitizeCell prefixes values that spreadsheet tools may interpret as formulas. -// Leading Unicode whitespace (including newlines) is ignored for detection only; -// the written cell keeps the original text with a leading single-quote escape. +// Leading spaces are ignored for formula detection only; tab and carriage return +// at the start are always escaped. The written cell keeps the original text with +// a leading single-quote when sanitization applies. func SanitizeCell(value string) string { if value == "" { return value } + if spreadsheetLeadingControl(value[0]) { + return "'" + value + } + trimmed := strings.TrimLeftFunc(value, unicode.IsSpace) if trimmed == "" { return value @@ -61,6 +66,15 @@ func SanitizeCell(value string) string { return value } +func spreadsheetLeadingControl(b byte) bool { + switch b { + case '\t', '\r': + return true + default: + return false + } +} + func formulaLeadingRune(r rune) bool { switch r { case '=', '+', '-', '@', '\\', '|', '%': diff --git a/pkg/safecsv/cell_test.go b/pkg/safecsv/cell_test.go index 7d2a754be..466c44169 100644 --- a/pkg/safecsv/cell_test.go +++ b/pkg/safecsv/cell_test.go @@ -38,6 +38,8 @@ func TestSanitizeCell(t *testing.T) { assert.Equal(t, "'@sum", SanitizeCell("@sum")) assert.Equal(t, "' =1+1", SanitizeCell(" =1+1")) assert.Equal(t, "'\n=1+1", SanitizeCell("\n=1+1")) + assert.Equal(t, "'\tplain", SanitizeCell("\tplain")) + assert.Equal(t, "'\rhello", SanitizeCell("\rhello")) assert.Equal(t, "'\\evil", SanitizeCell("\\evil")) assert.Equal(t, "'-5", SanitizeCell("-5")) }