From 2edc61f17159e4fde0f26098841160013015430b Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 23 Jan 2026 22:04:40 -0500 Subject: [PATCH] Fix panic when unmarshaling datetime values to incompatible types (#1028) (#1029) Return a type mismatch error instead of panicking when datetime values (DateTime, LocalDate, LocalTime, LocalDateTime) are unmarshaled into incompatible Go types. This makes the decoder safer for processing untrusted TOML input. https://claude.ai/code/session_011jwvtDS5M2KncLrqJpgMr5 Co-authored-by: Claude --- unmarshaler.go | 23 ++++++++++++++--------- unmarshaler_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/unmarshaler.go b/unmarshaler.go index 418fc7c..b61a347 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -852,6 +852,9 @@ func (d *decoder) unmarshalDateTime(value *unstable.Node, v reflect.Value) error return err } + if v.Kind() != reflect.Interface && v.Type() != timeType { + return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("datetime", v.Type())) + } v.Set(reflect.ValueOf(dt)) return nil } @@ -862,14 +865,14 @@ func (d *decoder) unmarshalLocalDate(value *unstable.Node, v reflect.Value) erro return err } + if v.Kind() != reflect.Interface && v.Type() != timeType { + return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("local date", v.Type())) + } if v.Type() == timeType { - cast := ld.AsTime(time.Local) - v.Set(reflect.ValueOf(cast)) + v.Set(reflect.ValueOf(ld.AsTime(time.Local))) return nil } - v.Set(reflect.ValueOf(ld)) - return nil } @@ -883,6 +886,9 @@ func (d *decoder) unmarshalLocalTime(value *unstable.Node, v reflect.Value) erro return unstable.NewParserError(rest, "extra characters at the end of a local time") } + if v.Kind() != reflect.Interface { + return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("local time", v.Type())) + } v.Set(reflect.ValueOf(lt)) return nil } @@ -897,15 +903,14 @@ func (d *decoder) unmarshalLocalDateTime(value *unstable.Node, v reflect.Value) return unstable.NewParserError(rest, "extra characters at the end of a local date time") } + if v.Kind() != reflect.Interface && v.Type() != timeType { + return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("local datetime", v.Type())) + } if v.Type() == timeType { - cast := ldt.AsTime(time.Local) - - v.Set(reflect.ValueOf(cast)) + v.Set(reflect.ValueOf(ldt.AsTime(time.Local))) return nil } - v.Set(reflect.ValueOf(ldt)) - return nil } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 9f66483..3e3b2a3 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -4344,3 +4344,44 @@ value = "b" }, }, cfg) } + +func TestIssue1028(t *testing.T) { + // Datetime values assigned to incompatible types should return an error, + // not panic. + + type Item struct { + Name string `toml:"name"` + } + + type Config struct { + Items map[string]Item `toml:"items"` + } + + // Error: "cannot decode TOML datetime into struct field Config.Items of type map[string]Item" + t.Run("OffsetDateTime", func(t *testing.T) { + var c Config + err := toml.Unmarshal([]byte(`items = 2023-01-01T10:20:30Z`), &c) + assert.Error(t, err) + }) + + // Error: "cannot decode TOML local datetime into struct field Config.Items of type map[string]Item" + t.Run("LocalDateTime", func(t *testing.T) { + var c Config + err := toml.Unmarshal([]byte(`items = 2023-01-01T10:20:30`), &c) + assert.Error(t, err) + }) + + // Error: "cannot decode TOML local date into struct field Config.Items of type map[string]Item" + t.Run("LocalDate", func(t *testing.T) { + var c Config + err := toml.Unmarshal([]byte(`items = 2023-01-01`), &c) + assert.Error(t, err) + }) + + // Error: "cannot decode TOML local time into struct field Config.Items of type map[string]Item" + t.Run("LocalTime", func(t *testing.T) { + var c Config + err := toml.Unmarshal([]byte(`items = 10:20:30`), &c) + assert.Error(t, err) + }) +}