fix(encode): propagate invalid UTF-8 key errors from headers and inline tables

Assisted-by: GLM 5.3
This commit is contained in:
2026-09-17 23:06:59 +02:00
parent ec0d7a0023
commit 54c6032a9a
3 changed files with 46 additions and 17 deletions
+4
View File
@@ -54,6 +54,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
cannot be carried verbatim by the literal form: a run of three single quotes,
a control character, or a lone carriage return. Such values previously
produced output that did not re-parse.
- `Marshal` returns an error for a table header key or an inline-table key that
is not valid UTF-8, the way scalar keys already did, instead of silently
emitting corrupt TOML (a header that lost its key, an inline table with a
missing key).
- A float with an exponent marker but no digits (`1e`, `0.0E`) is rejected;
the exponent requires at least one digit.
- A date-time offset outside 00:00 through 23:59 is rejected; such offsets
+31 -17
View File
@@ -579,7 +579,9 @@ func (e *encoder) emitDoc(doc *tomlDoc, prefix []string) error {
path := append(append([]string{}, prefix...), t.key)
e.writeBlankLine()
e.buf.WriteByte('[')
writeKeyPath(&e.buf, path)
if err := e.writeKeyPath(path); err != nil {
return err
}
e.buf.WriteString("]\n")
if err := e.emitDoc(t.doc, path); err != nil {
return err
@@ -590,7 +592,9 @@ func (e *encoder) emitDoc(doc *tomlDoc, prefix []string) error {
for _, sub := range a.docs {
e.writeBlankLine()
e.buf.WriteString("[[")
writeKeyPath(&e.buf, path)
if err := e.writeKeyPath(path); err != nil {
return err
}
e.buf.WriteString("]]\n")
if err := e.emitDoc(sub, path); err != nil {
return err
@@ -615,7 +619,9 @@ func (e *encoder) emitDoc(doc *tomlDoc, prefix []string) error {
path := append(append([]string{}, prefix...), ent.key)
e.writeBlankLine()
e.buf.WriteByte('[')
writeKeyPath(&e.buf, path)
if err := e.writeKeyPath(path); err != nil {
return err
}
e.buf.WriteString("]\n")
if err := e.emitDoc(ent.doc, path); err != nil {
return err
@@ -625,7 +631,9 @@ func (e *encoder) emitDoc(doc *tomlDoc, prefix []string) error {
for _, sub := range ent.docs {
e.writeBlankLine()
e.buf.WriteString("[[")
writeKeyPath(&e.buf, path)
if err := e.writeKeyPath(path); err != nil {
return err
}
e.buf.WriteString("]]\n")
if err := e.emitDoc(sub, path); err != nil {
return err
@@ -637,10 +645,9 @@ func (e *encoder) emitDoc(doc *tomlDoc, prefix []string) error {
}
func (e *encoder) writeKV(key string, val any) error {
if !utf8.ValidString(key) {
return fmt.Errorf("interpres: key %q is not valid UTF-8", key)
if err := e.writeKey(key); err != nil {
return err
}
e.writeKey(key)
e.buf.WriteString(" = ")
if err := e.writeValue(val); err != nil {
return err
@@ -649,25 +656,30 @@ func (e *encoder) writeKV(key string, val any) error {
return nil
}
func writeKeyPath(buf *bytes.Buffer, path []string) {
func (e *encoder) writeKeyPath(path []string) error {
for i, p := range path {
if i > 0 {
buf.WriteByte('.')
e.buf.WriteByte('.')
}
if isBareKey(p) {
buf.WriteString(p)
continue
if err := e.writeKey(p); err != nil {
return err
}
writeQuotedString(buf, p)
}
return nil
}
func (e *encoder) writeKey(key string) {
// writeKey writes one key, bare when it qualifies and quoted otherwise. A key
// that is not valid UTF-8 is an error; writing it anyway would emit corrupt
// TOML, because the quoted form has no representation for it.
func (e *encoder) writeKey(key string) error {
if isBareKey(key) {
e.buf.WriteString(key)
return
return nil
}
writeQuotedString(&e.buf, key)
if !utf8.ValidString(key) {
return fmt.Errorf("interpres: key %q is not valid UTF-8", key)
}
return writeQuotedString(&e.buf, key)
}
// writeQuotedString writes s as a TOML basic string (double-quoted) to buf.
@@ -787,7 +799,9 @@ func (e *encoder) writeInlineTable(m map[string]any) error {
if i > 0 {
e.buf.WriteString(", ")
}
e.writeKey(k)
if err := e.writeKey(k); err != nil {
return err
}
e.buf.WriteString(" = ")
if err := e.writeValue(m[k]); err != nil {
return err
+11
View File
@@ -1128,6 +1128,17 @@ func TestMarshalKeyRequiresUTF8(t *testing.T) {
if _, err := Marshal(m); err == nil {
t.Errorf("expected error for invalid UTF-8 key")
}
// The check must reach the keys of table headers and of inline tables
// nested inside value arrays, not only scalar keys: both write keys
// through the same path.
nested := map[string]any{"\xff": map[string]any{"k": "v"}}
if _, err := Marshal(nested); err == nil {
t.Errorf("expected error for invalid UTF-8 table header key")
}
inline := map[string]any{"mix": []any{1, map[string]any{"\xff": 1}}}
if _, err := Marshal(inline); err == nil {
t.Errorf("expected error for invalid UTF-8 inline table key")
}
}
func TestMarshalStringRequiresUTF8(t *testing.T) {