bug: word-wrapping long values corrupts multi-byte UTF-8 characters #14

Closed
opened 2026-09-14 19:20:58 +00:00 by advisor-bot · 0 comments
Owner

Problem

In encoding/systemd/ast.go, AssignNode.String() (~lines 314–333), when a value's rendered line would exceed 80 columns, the wrap loop does:

for i := range valueText {
    newValueBuilder.WriteByte(valueText[i])
    ...
}

Ranging over a Go string yields byte offsets at each rune boundary, but WriteByte only copies the single byte at that offset — the remaining bytes of any multi-byte UTF-8 rune are silently dropped. Any long value containing non-ASCII characters (accented letters, non-Latin scripts, emoji, etc.) comes out corrupted after round-tripping through the AST's String().

Reproduction

p := systemd.NewParserFromMemory("test", "[Section]\nDescription=héllo wörld ünïcode héllo wörld ünïcode héllo wörld ünïcode ...\n")
out := p.File.String()

produces mangled bytes such as h�llo w�rld instead of the original text, whenever the value is long enough to trigger the >80-column wrap path.

Iterate over the value by rune (e.g. []rune(valueText) or track byte ranges via utf8.DecodeRuneInString) and write whole runes (WriteRune/WriteString) instead of single bytes.

## Problem In `encoding/systemd/ast.go`, `AssignNode.String()` (~lines 314–333), when a value's rendered line would exceed 80 columns, the wrap loop does: ```go for i := range valueText { newValueBuilder.WriteByte(valueText[i]) ... } ``` Ranging over a Go string yields byte offsets at each *rune* boundary, but `WriteByte` only copies the single byte at that offset — the remaining bytes of any multi-byte UTF-8 rune are silently dropped. Any long value containing non-ASCII characters (accented letters, non-Latin scripts, emoji, etc.) comes out corrupted after round-tripping through the AST's `String()`. ## Reproduction ```go p := systemd.NewParserFromMemory("test", "[Section]\nDescription=héllo wörld ünïcode héllo wörld ünïcode héllo wörld ünïcode ...\n") out := p.File.String() ``` produces mangled bytes such as `h�llo w�rld` instead of the original text, whenever the value is long enough to trigger the >80-column wrap path. ## Recommended fix Iterate over the value by rune (e.g. `[]rune(valueText)` or track byte ranges via `utf8.DecodeRuneInString`) and write whole runes (`WriteRune`/`WriteString`) instead of single bytes.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pandora/systemd-file.gopack#14
No description provided.