fix(encode): propagate invalid UTF-8 key errors from headers and inline tables
Assisted-by: GLM 5.3
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user