Skip to content

Commit 06bcfb2

Browse files
committed
Check time zone offset elements
Too big parts of fractional hour time zone offset can cause assertion failures.
1 parent 59a6673 commit 06bcfb2

File tree

2 files changed

+40
-11
lines changed

2 files changed

+40
-11
lines changed

ext/date/date_parse.c

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -473,27 +473,53 @@ date_zone_to_diff(VALUE str)
473473
s++;
474474
l--;
475475

476+
#define out_of_range(v, min, max) ((v) < (min) || (max) < (v))
476477
hour = STRTOUL(s, &p, 10);
477478
if (*p == ':') {
479+
if (out_of_range(sec, 0, 59)) return Qnil;
478480
s = ++p;
479481
min = STRTOUL(s, &p, 10);
482+
if (out_of_range(min, 0, 59)) return Qnil;
480483
if (*p == ':') {
481484
s = ++p;
482485
sec = STRTOUL(s, &p, 10);
486+
if (out_of_range(hour, 0, 23)) return Qnil;
483487
}
484-
goto num;
485488
}
486-
if (*p == ',' || *p == '.') {
487-
char *e = 0;
488-
p++;
489-
min = STRTOUL(p, &e, 10) * 3600;
489+
else if (*p == ',' || *p == '.') {
490+
/* fractional hour */
491+
size_t n;
492+
int ov;
493+
/* no over precision for offset; 10**-7 hour = 0.36
494+
* milliseconds should be enough. */
495+
const size_t max_digits = 7; /* 36 * 10**7 < 32-bit FIXNUM_MAX */
496+
497+
if (out_of_range(hour, 0, 23)) return Qnil;
498+
499+
n = (s + l) - ++p;
500+
if (n > max_digits) n = max_digits;
501+
sec = ruby_scan_digits(p, n, 10, &n, &ov);
502+
if ((p += n) < s + l && *p >= ('5' + !(sec & 1)) && *p <= '9') {
503+
/* round half to even */
504+
sec++;
505+
}
506+
sec *= 36;
490507
if (sign) {
491508
hour = -hour;
492-
min = -min;
509+
sec = -sec;
510+
}
511+
if (n <= 2) {
512+
/* HH.nn or HH.n */
513+
if (n == 1) sec *= 10;
514+
offset = INT2FIX(sec + hour * 3600);
515+
}
516+
else {
517+
VALUE denom = rb_int_positive_pow(10, (int)(n - 2));
518+
offset = f_add(rb_rational_new(INT2FIX(sec), denom), INT2FIX(hour * 3600));
519+
if (rb_rational_den(offset) == INT2FIX(1)) {
520+
offset = rb_rational_num(offset);
521+
}
493522
}
494-
offset = rb_rational_new(INT2FIX(min),
495-
rb_int_positive_pow(10, (int)(e - p)));
496-
offset = f_add(INT2FIX(hour * 3600), offset);
497523
goto ok;
498524
}
499525
else if (l > 2) {
@@ -506,12 +532,11 @@ date_zone_to_diff(VALUE str)
506532
min = ruby_scan_digits(&s[2 - l % 2], 2, 10, &n, &ov);
507533
if (l >= 5)
508534
sec = ruby_scan_digits(&s[4 - l % 2], 2, 10, &n, &ov);
509-
goto num;
510535
}
511-
num:
512536
sec += min * 60 + hour * 3600;
513537
if (sign) sec = -sec;
514538
offset = INT2FIX(sec);
539+
#undef out_of_range
515540
}
516541
}
517542
}

test/date/test_date_strptime.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ def test__strptime__3
180180

181181
[['fri1feb034pm+5', '%a%d%b%y%H%p%Z'], [2003,2,1,16,nil,nil,'+5',5*3600,5]],
182182
[['E. Australia Standard Time', '%Z'], [nil,nil,nil,nil,nil,nil,'E. Australia Standard Time',10*3600,nil], __LINE__],
183+
184+
# out of range
185+
[['+0.9999999999999999999999', '%Z'], [nil,nil,nil,nil,nil,nil,'+0.9999999999999999999999',+1*3600,nil], __LINE__],
186+
[['+9999999999999999999999.0', '%Z'], [nil,nil,nil,nil,nil,nil,'+9999999999999999999999.0',nil,nil], __LINE__],
183187
].each do |x, y|
184188
h = Date._strptime(*x)
185189
a = h.values_at(:year,:mon,:mday,:hour,:min,:sec,:zone,:offset,:wday)

0 commit comments

Comments
 (0)