From d12fce7af3af27096b336f43700fffd51158e928 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 27 Sep 2022 22:46:06 +0900 Subject: [ruby/date] Check time zone offset elements Too big parts of fractional hour time zone offset can cause assertion failures. https://github.com/ruby/date/commit/06bcfb2729 --- ext/date/date_parse.c | 47 +++++++++++++++++++++++++++++++---------- test/date/test_date_strptime.rb | 4 ++++ 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/ext/date/date_parse.c b/ext/date/date_parse.c index 95274d5baa..4890401894 100644 --- a/ext/date/date_parse.c +++ b/ext/date/date_parse.c @@ -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) { @@ -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 } } } diff --git a/test/date/test_date_strptime.rb b/test/date/test_date_strptime.rb index fc42ebf7cd..521bf92916 100644 --- a/test/date/test_date_strptime.rb +++ b/test/date/test_date_strptime.rb @@ -180,6 +180,10 @@ class TestDateStrptime < Test::Unit::TestCase [['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) -- cgit v1.2.3