Skip to content

Commit 93a001b

Browse files
committed
Provide more precise error messages, using component ranges.
Signed-off-by: Andrew Gwozdziewycz <[email protected]>
1 parent 4303233 commit 93a001b

File tree

2 files changed

+43
-21
lines changed

2 files changed

+43
-21
lines changed

types/datetime.go

+31-12
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ func ParseDatetime(s string) (Datetime, error) {
7777
return Datetime{}, fmt.Errorf("%w: invalid month", errDatetime)
7878
}
7979
month = 10*int(rune(s[5])-'0') + int(rune(s[6])-'0')
80+
if month > 12 {
81+
return Datetime{}, fmt.Errorf("%w: month is out of range", errDatetime)
82+
}
8083

8184
if s[7] != '-' {
8285
return Datetime{}, fmt.Errorf("%w: unexpected character %s", errDatetime, strconv.QuoteRune(rune(s[7])))
@@ -88,6 +91,9 @@ func ParseDatetime(s string) (Datetime, error) {
8891
return Datetime{}, fmt.Errorf("%w: invalid day", errDatetime)
8992
}
9093
day = 10*int(rune(s[8])-'0') + int(rune(s[9])-'0')
94+
if day > 31 {
95+
return Datetime{}, fmt.Errorf("%w: day is out of range", errDatetime)
96+
}
9197

9298
// If the length is 10, we only have a date and we're done.
9399
if length == 10 {
@@ -117,6 +123,9 @@ func ParseDatetime(s string) (Datetime, error) {
117123
return Datetime{}, fmt.Errorf("%w: invalid hour", errDatetime)
118124
}
119125
hour = 10*int(rune(s[11])-'0') + int(rune(s[12])-'0')
126+
if hour > 23 {
127+
return Datetime{}, fmt.Errorf("%w: hour is out of range", errDatetime)
128+
}
120129

121130
if s[13] != ':' {
122131
return Datetime{}, fmt.Errorf("%w: unexpected character %s", errDatetime, strconv.QuoteRune(rune(s[13])))
@@ -127,6 +136,9 @@ func ParseDatetime(s string) (Datetime, error) {
127136
return Datetime{}, fmt.Errorf("%w: invalid minute", errDatetime)
128137
}
129138
minute = 10*int(rune(s[14])-'0') + int(rune(s[15])-'0')
139+
if minute > 59 {
140+
return Datetime{}, fmt.Errorf("%w: minute is out of range", errDatetime)
141+
}
130142

131143
if s[16] != ':' {
132144
return Datetime{}, fmt.Errorf("%w: unexpected character %s", errDatetime, strconv.QuoteRune(rune(s[16])))
@@ -137,6 +149,9 @@ func ParseDatetime(s string) (Datetime, error) {
137149
return Datetime{}, fmt.Errorf("%w: invalid second", errDatetime)
138150
}
139151
second = 10*int(rune(s[17])-'0') + int(rune(s[18])-'0')
152+
if second > 59 {
153+
return Datetime{}, fmt.Errorf("%w: second is out of range", errDatetime)
154+
}
140155

141156
// At this point, things are variable.
142157
// 19 can be ., in which case we have milliseconds. (SSS)
@@ -189,9 +204,17 @@ func ParseDatetime(s string) (Datetime, error) {
189204
return Datetime{}, fmt.Errorf("%w: invalid time zone offset", errDatetime)
190205
}
191206

192-
hh := time.Duration(10*int64(rune(s[trailerOffset+1])-'0')+int64(rune(s[trailerOffset+2])-'0')) * time.Hour
193-
mm := time.Duration(10*int64(rune(s[trailerOffset+3])-'0')+int64(rune(s[trailerOffset+4])-'0')) * time.Minute
194-
offset = time.Duration(sign) * (hh + mm)
207+
hh := time.Duration(10*int64(rune(s[trailerOffset+1])-'0') + int64(rune(s[trailerOffset+2])-'0'))
208+
mm := time.Duration(10*int64(rune(s[trailerOffset+3])-'0') + int64(rune(s[trailerOffset+4])-'0'))
209+
210+
if hh > 23 {
211+
return Datetime{}, fmt.Errorf("%w: time zone offset hours are out of range", errDatetime)
212+
}
213+
if mm > 59 {
214+
return Datetime{}, fmt.Errorf("%w: time zone offset minutes are out of range", errDatetime)
215+
}
216+
217+
offset = time.Duration(sign) * ((hh * time.Hour) + (mm * time.Minute))
195218

196219
default:
197220
return Datetime{}, fmt.Errorf("%w: invalid time zone designator", errDatetime)
@@ -201,15 +224,11 @@ func ParseDatetime(s string) (Datetime, error) {
201224
hour, minute, second,
202225
int(time.Duration(milli)*time.Millisecond), time.UTC)
203226

204-
// Don't allow wrapping: https://github.com/cedar-policy/rfcs/pull/94
205-
tyear, tmonth, tday := t.Date()
206-
if year != tyear || time.Month(month) != tmonth || day != tday {
207-
return Datetime{}, fmt.Errorf("%w: date would wrap", errDatetime)
208-
}
209-
210-
thour, tminute, tsecond := t.Hour(), t.Minute(), t.Second()
211-
if hour != thour || minute != tminute || second != tsecond {
212-
return Datetime{}, fmt.Errorf("%w: time would wrap", errDatetime)
227+
// Don't allow wrapping: https://github.com/cedar-policy/rfcs/pull/94, which can occur
228+
// because not all months have 31 days, which is our validation range
229+
_, tmonth, tday := t.Date()
230+
if time.Month(month) != tmonth || day != tday {
231+
return Datetime{}, fmt.Errorf("%w: invalid date", errDatetime)
213232
}
214233

215234
t = t.Add(offset)

types/datetime_test.go

+12-9
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,18 @@ func TestDatetime(t *testing.T) {
102102
{"1995-01-01T00:00:00.000-aaaa0", "error parsing datetime value: unexpected trailer after time zone designator"},
103103

104104
// Prevent Wrapping invalid dates to real dates: See: cedar-policy/rfcs#94
105-
{"2024-02-30T00:00:00Z", "error parsing datetime value: date would wrap"},
106-
{"2024-02-29T23:59:60Z", "error parsing datetime value: date would wrap"},
107-
{"2023-02-28T23:59:60Z", "error parsing datetime value: date would wrap"},
108-
{"1970-01-01T25:00:00Z", "error parsing datetime value: date would wrap"},
109-
{"1970-01-32T00:00:00Z", "error parsing datetime value: date would wrap"},
110-
{"1970-13-01T00:00:00Z", "error parsing datetime value: date would wrap"},
111-
112-
{"1970-01-01T00:00:60Z", "error parsing datetime value: time would wrap"},
113-
{"1970-01-01T00:60:00Z", "error parsing datetime value: time would wrap"},
105+
{"2024-02-30T00:00:00Z", "error parsing datetime value: invalid date"},
106+
{"2024-02-29T23:59:60Z", "error parsing datetime value: second is out of range"},
107+
{"2023-02-28T23:59:60Z", "error parsing datetime value: second is out of range"},
108+
{"2023-02-28T23:60:59Z", "error parsing datetime value: minute is out of range"},
109+
{"1970-01-01T25:00:00Z", "error parsing datetime value: hour is out of range"},
110+
{"1970-12-32T:00:00Z", "error parsing datetime value: day is out of range"},
111+
{"1970-13-01T00:00:00Z", "error parsing datetime value: month is out of range"},
112+
113+
{"1970-01-01T00:00:00+2400", "error parsing datetime value: time zone offset hours are out of range"},
114+
{"1970-01-01T00:00:00-2400", "error parsing datetime value: time zone offset hours are out of range"},
115+
{"1970-01-01T00:00:00+2360", "error parsing datetime value: time zone offset minutes are out of range"},
116+
{"1970-01-01T00:00:00-2360", "error parsing datetime value: time zone offset minutes are out of range"},
114117
}
115118
for ti, tt := range tests {
116119
tt := tt

0 commit comments

Comments
 (0)