fix: reject an out-of-range date-time offset

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-17 21:30:34 +02:00
parent e19a6f35f1
commit 3cd538fad6
2 changed files with 42 additions and 0 deletions
+14
View File
@@ -6,6 +6,7 @@ package interpres
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strconv"
"strings" "strings"
"time" "time"
) )
@@ -78,6 +79,12 @@ var dateTimeShape = regexp.MustCompile(
`|^\d{2}:\d{2}:\d{2}(\.\d+)?$`, `|^\d{2}:\d{2}:\d{2}(\.\d+)?$`,
) )
// offsetBounds extracts the numeric offset of a date-time. The ABNF bounds it
// to 00:00 through 23:59, but time.Parse accepts values outside that range
// and rolls them over (for example "+00:60" becomes "+01:00"), so the bounds
// are enforced here.
var offsetBounds = regexp.MustCompile(`([+-])(\d{2}):(\d{2})$`)
// parseDateTime classifies and parses a bare token as a TOML date-time value. // parseDateTime classifies and parses a bare token as a TOML date-time value.
// It returns the decoded value (time.Time, LocalDateTime, LocalDate, or // It returns the decoded value (time.Time, LocalDateTime, LocalDate, or
// LocalTime) and whether the token was a date-time at all. // LocalTime) and whether the token was a date-time at all.
@@ -91,6 +98,13 @@ func parseDateTime(tok string) (any, bool) {
if !dateTimeShape.MatchString(tok) { if !dateTimeShape.MatchString(tok) {
return nil, false return nil, false
} }
if m := offsetBounds.FindStringSubmatch(tok); m != nil {
hour, _ := strconv.Atoi(m[2])
minute, _ := strconv.Atoi(m[3])
if hour > 23 || minute > 59 {
return nil, false
}
}
// The ABNF accepts lowercase "t"/"z"; time.Parse only matches uppercase. // The ABNF accepts lowercase "t"/"z"; time.Parse only matches uppercase.
norm := strings.ToUpper(tok) norm := strings.ToUpper(tok)
for _, layout := range offsetDateTimeLayouts { for _, layout := range offsetDateTimeLayouts {
+28
View File
@@ -380,6 +380,34 @@ func TestRejectsInvalidNumbers(t *testing.T) {
} }
} }
func TestParseRejectsOffsetOutOfRange(t *testing.T) {
for _, tok := range []string{
"1979-05-27T07:32:00+00:60",
"1979-05-27T07:32:00-00:99",
"1979-05-27T07:32:00+24:00",
"1979-05-27T07:32:00+99:99",
} {
if _, err := Parse([]byte("v = " + tok + "\n")); err == nil {
t.Errorf("%q: expected an error, got none", tok)
}
}
}
func TestParseAcceptsOffsetBounds(t *testing.T) {
tree, err := Parse([]byte("a = 1979-05-27T07:32:00+23:59\nb = 1979-05-27T07:32:00-23:59\n"))
if err != nil {
t.Fatalf("parse: %v", err)
}
a := tree["a"].(time.Time)
if _, offset := a.Zone(); offset != 23*3600+59*60 {
t.Fatalf("a offset = %d, want %d", offset, 23*3600+59*60)
}
b := tree["b"].(time.Time)
if _, offset := b.Zone(); offset != -(23*3600 + 59*60) {
t.Fatalf("b offset = %d", offset)
}
}
func TestAcceptsNumberEdgeCases(t *testing.T) { func TestAcceptsNumberEdgeCases(t *testing.T) {
cases := map[string]any{ cases := map[string]any{
"0": int64(0), "0": int64(0),