Skip to content

Commit 56ed3ee

Browse files
author
Andrew Gwozdziewycz
authored
Merge pull request #81 from cedar-policy/apg/rfcs-94-prevent-time-wrapping
Addresses cedar-policy/rfcs#94 concerns about time wrapping
2 parents d7b68aa + 38755cf commit 56ed3ee

File tree

2 files changed

+52
-3
lines changed

2 files changed

+52
-3
lines changed

types/datetime.go

Lines changed: 34 additions & 3 deletions
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)
@@ -200,6 +223,14 @@ func ParseDatetime(s string) (Datetime, error) {
200223
t := time.Date(year, time.Month(month), day,
201224
hour, minute, second,
202225
int(time.Duration(milli)*time.Millisecond), time.UTC)
226+
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)
232+
}
233+
203234
t = t.Add(offset)
204235
return Datetime{value: t.UnixMilli()}, nil
205236
}

types/datetime_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ func TestDatetime(t *testing.T) {
4242
{"1970-01-01T00:10:00-0010", "1970-01-01T00:00:00.000Z"},
4343
{"1970-01-01T01:00:00-0100", "1970-01-01T00:00:00.000Z"},
4444
{"1970-01-01T10:00:00-1000", "1970-01-01T00:00:00.000Z"},
45+
46+
{"1972-02-29T10:00:00-1000", "1972-02-29T00:00:00.000Z"},
4547
}
4648
for ti, tt := range tests {
4749
tt := tt
@@ -100,6 +102,22 @@ func TestDatetime(t *testing.T) {
100102
{"1995-01-01T00:00:00.000-0aaa", "error parsing datetime value: invalid time zone offset"},
101103
{"1995-01-01T00:00:00.000-aaaa", "error parsing datetime value: invalid time zone offset"},
102104
{"1995-01-01T00:00:00.000-aaaa0", "error parsing datetime value: unexpected trailer after time zone designator"},
105+
106+
{"1995-04-31T00:00:00Z", "error parsing datetime value: invalid date"},
107+
108+
// Prevent Wrapping invalid dates to real dates: See: cedar-policy/rfcs#94
109+
{"2024-02-30T00:00:00Z", "error parsing datetime value: invalid date"},
110+
{"2024-02-29T23:59:60Z", "error parsing datetime value: second is out of range"},
111+
{"2023-02-28T23:59:60Z", "error parsing datetime value: second is out of range"},
112+
{"2023-02-28T23:60:59Z", "error parsing datetime value: minute is out of range"},
113+
{"1970-01-01T25:00:00Z", "error parsing datetime value: hour is out of range"},
114+
{"1970-12-32T:00:00Z", "error parsing datetime value: day is out of range"},
115+
{"1970-13-01T00:00:00Z", "error parsing datetime value: month is out of range"},
116+
117+
{"1970-01-01T00:00:00+2400", "error parsing datetime value: time zone offset hours are out of range"},
118+
{"1970-01-01T00:00:00-2400", "error parsing datetime value: time zone offset hours are out of range"},
119+
{"1970-01-01T00:00:00+2360", "error parsing datetime value: time zone offset minutes are out of range"},
120+
{"1970-01-01T00:00:00-2360", "error parsing datetime value: time zone offset minutes are out of range"},
103121
}
104122
for ti, tt := range tests {
105123
tt := tt

0 commit comments

Comments
 (0)