Skip to content

Commit

Permalink
[ruby/date] Check time zone offset elements
Browse files Browse the repository at this point in the history
Too big parts of fractional hour time zone offset can cause assertion
failures.

ruby/date@06bcfb2729
  • Loading branch information
nobu authored and matzbot committed Sep 28, 2022
1 parent cd77e71 commit d12fce7
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 11 deletions.
47 changes: 36 additions & 11 deletions ext/date/date_parse.c
Expand Up @@ -473,27 +473,53 @@ date_zone_to_diff(VALUE str)
s++;
l--;

#define out_of_range(v, min, max) ((v) < (min) || (max) < (v))
hour = STRTOUL(s, &p, 10);
if (*p == ':') {
if (out_of_range(sec, 0, 59)) return Qnil;
s = ++p;
min = STRTOUL(s, &p, 10);
if (out_of_range(min, 0, 59)) return Qnil;
if (*p == ':') {
s = ++p;
sec = STRTOUL(s, &p, 10);
if (out_of_range(hour, 0, 23)) return Qnil;
}
goto num;
}
if (*p == ',' || *p == '.') {
char *e = 0;
p++;
min = STRTOUL(p, &e, 10) * 3600;
else if (*p == ',' || *p == '.') {
/* fractional hour */
size_t n;
int ov;
/* no over precision for offset; 10**-7 hour = 0.36
* milliseconds should be enough. */
const size_t max_digits = 7; /* 36 * 10**7 < 32-bit FIXNUM_MAX */

if (out_of_range(hour, 0, 23)) return Qnil;

n = (s + l) - ++p;
if (n > max_digits) n = max_digits;
sec = ruby_scan_digits(p, n, 10, &n, &ov);
if ((p += n) < s + l && *p >= ('5' + !(sec & 1)) && *p <= '9') {
/* round half to even */
sec++;
}
sec *= 36;
if (sign) {
hour = -hour;
min = -min;
sec = -sec;
}
if (n <= 2) {
/* HH.nn or HH.n */
if (n == 1) sec *= 10;
offset = INT2FIX(sec + hour * 3600);
}
else {
VALUE denom = rb_int_positive_pow(10, (int)(n - 2));
offset = f_add(rb_rational_new(INT2FIX(sec), denom), INT2FIX(hour * 3600));
if (rb_rational_den(offset) == INT2FIX(1)) {
offset = rb_rational_num(offset);
}
}
offset = rb_rational_new(INT2FIX(min),
rb_int_positive_pow(10, (int)(e - p)));
offset = f_add(INT2FIX(hour * 3600), offset);
goto ok;
}
else if (l > 2) {
Expand All @@ -506,12 +532,11 @@ date_zone_to_diff(VALUE str)
min = ruby_scan_digits(&s[2 - l % 2], 2, 10, &n, &ov);
if (l >= 5)
sec = ruby_scan_digits(&s[4 - l % 2], 2, 10, &n, &ov);
goto num;
}
num:
sec += min * 60 + hour * 3600;
if (sign) sec = -sec;
offset = INT2FIX(sec);
#undef out_of_range
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions test/date/test_date_strptime.rb
Expand Up @@ -180,6 +180,10 @@ def test__strptime__3

[['fri1feb034pm+5', '%a%d%b%y%H%p%Z'], [2003,2,1,16,nil,nil,'+5',5*3600,5]],
[['E. Australia Standard Time', '%Z'], [nil,nil,nil,nil,nil,nil,'E. Australia Standard Time',10*3600,nil], __LINE__],

# out of range
[['+0.9999999999999999999999', '%Z'], [nil,nil,nil,nil,nil,nil,'+0.9999999999999999999999',+1*3600,nil], __LINE__],
[['+9999999999999999999999.0', '%Z'], [nil,nil,nil,nil,nil,nil,'+9999999999999999999999.0',nil,nil], __LINE__],
].each do |x, y|
h = Date._strptime(*x)
a = h.values_at(:year,:mon,:mday,:hour,:min,:sec,:zone,:offset,:wday)
Expand Down

0 comments on commit d12fce7

Please sign in to comment.