From 5367e5353874945c1770b352cbc1e8b1940c6792 Mon Sep 17 00:00:00 2001 From: youdie006 Date: Thu, 10 Sep 2026 09:27:30 +0900 Subject: [PATCH] Only allow "T" or a space as the RFC 3339 separator Time.rfc3339's pattern used [T\s], so it accepted a tab, newline, vertical tab, form feed or carriage return between the date and the time. Time.xmlschema's pattern has a bare T. RFC 3339 section 5.6 defines the separator as "T" and only notes that an application may use a space for readability. The other \s characters are not permitted by either. Since Ruby 3.2 the pattern also disagrees with what the code does with the string it matched. _xmlschema hands it to Time.new, which rejects those five characters, so the documented ArgumentError never gets raised and Time.new's internal message escapes instead: Time.rfc3339("2011-10-05\t22:26:12Z") #=> ArgumentError: "+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" # expected for utc_offset: 22:26:12Z Narrowing the class to [T ] makes the pattern agree with the RFC, with Time.xmlschema, and with what _xmlschema can actually parse, so these inputs now raise "invalid rfc3339 format:" as documented. --- lib/time.rb | 2 +- test/test_time.rb | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/time.rb b/lib/time.rb index b157e60..776c82c 100644 --- a/lib/time.rb +++ b/lib/time.rb @@ -658,7 +658,7 @@ def xmlschema(time) def rfc3339(time) pattern = /\A\s* (-?\d{4})-(\d\d)-(\d\d) - [T\s] + [T ] (\d\d):(\d\d):(\d\d) (\.\d+)? (Z|[+-]\d\d:\d\d) diff --git a/test/test_time.rb b/test/test_time.rb index 53ac856..4054bc4 100644 --- a/test/test_time.rb +++ b/test/test_time.rb @@ -607,6 +607,29 @@ def test_huge_precision define_method(test.sub(/xmlschema/, 'rfc3339')) {__send__(sub, :rfc3339)} end + def test_rfc3339_separator + # RFC 3339 section 5.6 defines the separator as "T", and only notes that an + # application may use a space for readability. The other \s characters are + # not permitted, and Time.xmlschema has never accepted any of them. + t = Time.utc(2011, 10, 5, 22, 26, 12) + assert_equal(t, Time.rfc3339("2011-10-05T22:26:12Z")) + assert_equal(t, Time.rfc3339("2011-10-05 22:26:12Z")) + + ["\t", "\n", "\v", "\f", "\r"].each do |sep| + s = "2011-10-05#{sep}22:26:12Z" + e = assert_raise(ArgumentError, "separator #{sep.inspect}") { Time.rfc3339(s) } + assert_match(/invalid rfc3339 format/, e.message, "separator #{sep.inspect}") + end + + # Time.xmlschema keeps rejecting every separator but "T". + assert_equal(t, Time.xmlschema("2011-10-05T22:26:12Z")) + ["\t", "\n", "\v", "\f", "\r", " "].each do |sep| + assert_raise(ArgumentError, "separator #{sep.inspect}") do + Time.xmlschema("2011-10-05#{sep}22:26:12Z") + end + end + end + def test_parse_with_various_object d = Date.new(2010, 10, 28) dt = DateTime.new(2010, 10, 28)