Skip to content

Commit 86cb731

Browse files
committed
fix(output): truncate progress item text on rune boundaries
lastItem was sliced by byte index, so a multibyte character straddling the cutoff produced invalid UTF-8 in the progress line. truncate on rune boundaries instead.
1 parent cfdd7be commit 86cb731

4 files changed

Lines changed: 9 additions & 6 deletions

File tree

internal/output/output.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727
// rewrite the terminal title, move the cursor, or clear the screen when it
2828
// reaches the sink. \t and \n survive so tab/newline-structured output stays
2929
// intact, and SGR color sequences (ESC [ ... m, what lipgloss emits for
30-
// bold/color) survive so legitimate styling keeps working - every other C0
30+
// bold/color) survive so legitimate styling keeps working: every other C0
3131
// control byte, DEL (0x7f), C1 controls (U+0080-U+009F), OSC sequences
3232
// (ESC ] ... BEL/ST) and non-SGR CSI sequences (cursor moves, screen clears,
3333
// ...) are dropped.

internal/output/progress.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"fmt"
1717
"sync"
1818
"sync/atomic"
19+
"unicode/utf8"
1920
)
2021

2122
// Progress bar configuration
@@ -174,10 +175,12 @@ func (p *Progress) render() {
174175
}
175176
}
176177

177-
// Truncate item if too long
178+
// rune-aware so a multibyte character straddling the cut point isn't
179+
// split into invalid UTF-8.
178180
maxItemLen := 30
179-
if len(lastItem) > maxItemLen {
180-
lastItem = lastItem[:maxItemLen-3] + "..."
181+
if runeCount := utf8.RuneCountInString(lastItem); runeCount > maxItemLen {
182+
runes := []rune(lastItem)
183+
lastItem = string(runes[:maxItemLen-3]) + "..."
181184
}
182185

183186
// Format: [========> ] 45% (4500/10000) /admin

internal/output/sanitize_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestInfoStripsRawANSI(t *testing.T) {
3737
t.Logf("CONFIRMED: Info sanitizes control bytes before printing: %q", got)
3838
}
3939

40-
// SGR color sequences (what lipgloss emits) must survive sanitization -
40+
// SGR color sequences (what lipgloss emits) must survive sanitization:
4141
// Sanitize is meant to strip attacker control bytes, not legitimate styling.
4242
func TestSanitizeKeepsSGRColor(t *testing.T) {
4343
styled := "\x1b[1;38;5;231mhello\x1b[0m"

internal/scan/probe_ansi_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"github.com/vmfunc/sif/internal/output"
1313
)
1414

15-
// extractTitle deliberately returns the raw <title> text unmodified - it's a
15+
// extractTitle deliberately returns the raw <title> text unmodified: it's a
1616
// data-extraction helper, and the JSON/log record of a probe result should be
1717
// byte-accurate. sanitization happens downstream, at the point the title is
1818
// printed to the terminal (see TestProbeTitleANSIStrippedFromTerminal below).

0 commit comments

Comments
 (0)