Escape CSV cells that start with tab or carriage return

Trim-based formula detection skipped the old rule for
leading control characters; check those bytes before trim.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
This commit is contained in:
Cursor Agent
2026-07-30 07:32:54 +00:00
parent 90b3258f32
commit b1e6949fe4
2 changed files with 18 additions and 2 deletions

View File

@@ -41,13 +41,18 @@ func SanitizeRecord(record []string) []string {
} }
// SanitizeCell prefixes values that spreadsheet tools may interpret as formulas. // SanitizeCell prefixes values that spreadsheet tools may interpret as formulas.
// Leading Unicode whitespace (including newlines) is ignored for detection only; // Leading spaces are ignored for formula detection only; tab and carriage return
// the written cell keeps the original text with a leading single-quote escape. // 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 { func SanitizeCell(value string) string {
if value == "" { if value == "" {
return value return value
} }
if spreadsheetLeadingControl(value[0]) {
return "'" + value
}
trimmed := strings.TrimLeftFunc(value, unicode.IsSpace) trimmed := strings.TrimLeftFunc(value, unicode.IsSpace)
if trimmed == "" { if trimmed == "" {
return value return value
@@ -61,6 +66,15 @@ func SanitizeCell(value string) string {
return value return value
} }
func spreadsheetLeadingControl(b byte) bool {
switch b {
case '\t', '\r':
return true
default:
return false
}
}
func formulaLeadingRune(r rune) bool { func formulaLeadingRune(r rune) bool {
switch r { switch r {
case '=', '+', '-', '@', '\\', '|', '%': case '=', '+', '-', '@', '\\', '|', '%':

View File

@@ -38,6 +38,8 @@ func TestSanitizeCell(t *testing.T) {
assert.Equal(t, "'@sum", SanitizeCell("@sum")) assert.Equal(t, "'@sum", SanitizeCell("@sum"))
assert.Equal(t, "' =1+1", SanitizeCell(" =1+1")) assert.Equal(t, "' =1+1", SanitizeCell(" =1+1"))
assert.Equal(t, "'\n=1+1", SanitizeCell("\n=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, "'\\evil", SanitizeCell("\\evil"))
assert.Equal(t, "'-5", SanitizeCell("-5")) assert.Equal(t, "'-5", SanitizeCell("-5"))
} }