diff --git a/lib/tzinfo.rb b/lib/tzinfo.rb index 96e94ade..abac4419 100644 --- a/lib/tzinfo.rb +++ b/lib/tzinfo.rb @@ -7,6 +7,8 @@ module TZInfo require_relative 'tzinfo/version' +require_relative 'tzinfo/untaint_ext' if RUBY_VERSION >= '2.7' + require_relative 'tzinfo/string_deduper' require_relative 'tzinfo/timestamp' diff --git a/lib/tzinfo/data_sources/ruby_data_source.rb b/lib/tzinfo/data_sources/ruby_data_source.rb index f99fafde..bc608e84 100644 --- a/lib/tzinfo/data_sources/ruby_data_source.rb +++ b/lib/tzinfo/data_sources/ruby_data_source.rb @@ -2,6 +2,8 @@ # frozen_string_literal: true module TZInfo + using UntaintExt if TZInfo.const_defined?(:UntaintExt) + module DataSources # A {TZInfoDataNotFound} exception is raised if the tzinfo-data gem could # not be found (i.e. `require 'tzinfo/data'` failed) when selecting the Ruby diff --git a/lib/tzinfo/data_sources/zoneinfo_data_source.rb b/lib/tzinfo/data_sources/zoneinfo_data_source.rb index d23328c6..ea58d203 100644 --- a/lib/tzinfo/data_sources/zoneinfo_data_source.rb +++ b/lib/tzinfo/data_sources/zoneinfo_data_source.rb @@ -2,6 +2,8 @@ # frozen_string_literal: true module TZInfo + using UntaintExt if TZInfo.const_defined?(:UntaintExt) + module DataSources # An {InvalidZoneinfoDirectory} exception is raised if {ZoneinfoDataSource} # is initialized with a specific zoneinfo path that is not a valid zoneinfo diff --git a/lib/tzinfo/data_sources/zoneinfo_reader.rb b/lib/tzinfo/data_sources/zoneinfo_reader.rb index f9e3906a..b74d162a 100644 --- a/lib/tzinfo/data_sources/zoneinfo_reader.rb +++ b/lib/tzinfo/data_sources/zoneinfo_reader.rb @@ -2,6 +2,8 @@ # frozen_string_literal: true module TZInfo + using UntaintExt if TZInfo.const_defined?(:UntaintExt) + module DataSources # An {InvalidZoneinfoFile} exception is raised if an attempt is made to load # an invalid zoneinfo file. diff --git a/lib/tzinfo/untaint_ext.rb b/lib/tzinfo/untaint_ext.rb new file mode 100644 index 00000000..ace0f37f --- /dev/null +++ b/lib/tzinfo/untaint_ext.rb @@ -0,0 +1,18 @@ +# encoding: UTF-8 +# frozen_string_literal: true + +module TZInfo + # Object#untaint is deprecated in Ruby >= 2.7 and will be removed in 3.0. + # UntaintExt adds a refinement to make Object#untaint a no-op and avoid the + # warning. + # + # @private + module UntaintExt # :nodoc: + refine Object do + def untaint + self + end + end + end + private_constant :UntaintExt +end diff --git a/test/data_sources/tc_ruby_data_source.rb b/test/data_sources/tc_ruby_data_source.rb index 89851c6a..21259a48 100644 --- a/test/data_sources/tc_ruby_data_source.rb +++ b/test/data_sources/tc_ruby_data_source.rb @@ -3,6 +3,8 @@ require_relative '../test_utils' +using TestUtils::TaintExt if TestUtils.const_defined?(:TaintExt) + include TZInfo module DataSources @@ -118,7 +120,7 @@ def test_load_timezone_info_minus end def test_load_timezone_info_tainted - safe_test do + safe_test(unavailable: :skip) do identifier = 'Europe/Amsterdam'.dup.taint assert(identifier.tainted?) info = @data_source.send(:load_timezone_info, identifier) @@ -227,7 +229,7 @@ def test_load_country_info_case end def test_load_country_info_tainted - safe_test do + safe_test(unavailable: :skip) do code = 'NL'.dup.taint assert(code.tainted?) info = @data_source.send(:load_country_info, code) diff --git a/test/data_sources/tc_zoneinfo_data_source.rb b/test/data_sources/tc_zoneinfo_data_source.rb index b8a40cdd..9da3ede9 100644 --- a/test/data_sources/tc_zoneinfo_data_source.rb +++ b/test/data_sources/tc_zoneinfo_data_source.rb @@ -8,6 +8,9 @@ include TZInfo +using TestUtils::TaintExt if TestUtils.const_defined?(:TaintExt) +using UntaintExt if TZInfo.const_defined?(:UntaintExt) + module DataSources class TCZoneinfoDataSource < Minitest::Test ZONEINFO_DIR = File.expand_path(File.join(File.dirname(__FILE__), '..', 'zoneinfo')).untaint @@ -778,7 +781,7 @@ def test_load_timezone_info_linked_relative_parent_inside end def test_load_timezone_info_tainted - safe_test do + safe_test(unavailable: :skip) do identifier = 'Europe/Amsterdam'.dup.taint assert(identifier.tainted?) info = @data_source.send(:load_timezone_info, identifier) @@ -1120,7 +1123,7 @@ def test_load_country_info_case end def test_load_country_info_tainted - safe_test do + safe_test(unavailable: :skip) do code = 'NL'.dup.taint assert(code.tainted?) info = @data_source.send(:load_country_info, code) diff --git a/test/data_sources/tc_zoneinfo_reader.rb b/test/data_sources/tc_zoneinfo_reader.rb index 0b916b5f..1c7e10d1 100644 --- a/test/data_sources/tc_zoneinfo_reader.rb +++ b/test/data_sources/tc_zoneinfo_reader.rb @@ -6,6 +6,8 @@ include TZInfo +using UntaintExt if TZInfo.const_defined?(:UntaintExt) + module DataSources class TCZoneinfoReader < Minitest::Test MIN_FORMAT = 1 @@ -1216,10 +1218,10 @@ def test_read_untainted_in_safe_mode o0 = TimezoneOffset.new(-12094, 0, 'LT') tzif_test(offsets, []) do |path, format| - # Temp file path is tainted with Ruby >= 2.3.0. Untaint for this test. - path.untaint - safe_test do + # Temp file path is tainted with Ruby >= 2.3.0. Untaint for this test. + path.untaint + assert_equal(o0, @reader.read(path)) end end @@ -1229,10 +1231,10 @@ def test_read_tainted_in_safe_mode offsets = [{gmtoff: -12094, isdst: false, abbrev: 'LT'}] tzif_test(offsets, []) do |path, format| - # Temp file path is only tainted with Ruby >= 2.3.0. Taint for this test. - path.taint - safe_test(unavailable: :skip) do + # Temp file path is only tainted with Ruby >= 2.3.0. Taint for this test. + path.taint + assert_raises(SecurityError) { @reader.read(path) } end end diff --git a/test/tc_country.rb b/test/tc_country.rb index 1783e0bd..61a1b5eb 100644 --- a/test/tc_country.rb +++ b/test/tc_country.rb @@ -5,6 +5,8 @@ include TZInfo +using TestUtils::TaintExt if TestUtils.const_defined?(:TaintExt) + class TCCountry < Minitest::Test def setup @orig_data_source = DataSource.get @@ -46,7 +48,7 @@ def test_get_nil def test_get_tainted_loaded Country.get('GB') - safe_test do + safe_test(unavailable: :skip) do code = 'GB'.dup.taint assert(code.tainted?) country = Country.get(code) @@ -65,7 +67,7 @@ def test_get_tainted_and_frozen_loaded end def test_get_tainted_not_previously_loaded - safe_test do + safe_test(unavailable: :skip) do code = 'GB'.dup.taint assert(code.tainted?) country = Country.get(code) diff --git a/test/tc_timezone.rb b/test/tc_timezone.rb index 55e90716..a6a2d7d1 100644 --- a/test/tc_timezone.rb +++ b/test/tc_timezone.rb @@ -5,6 +5,8 @@ include TZInfo +using TestUtils::TaintExt if TestUtils.const_defined?(:TaintExt) + class TCTimezone < Minitest::Test class << self private @@ -258,7 +260,7 @@ def test_get_nil def test_get_tainted_loaded Timezone.get('Europe/Andorra') - safe_test do + safe_test(unavailable: :skip) do identifier = 'Europe/Andorra'.dup.taint assert(identifier.tainted?) tz = Timezone.get(identifier) @@ -277,7 +279,7 @@ def test_get_tainted_and_frozen_loaded end def test_get_tainted_not_previously_loaded - safe_test do + safe_test(unavailable: :skip) do identifier = 'Europe/Andorra'.dup.taint assert(identifier.tainted?) tz = Timezone.get(identifier) diff --git a/test/test_utils.rb b/test/test_utils.rb index a0d1fce3..d81abda7 100644 --- a/test/test_utils.rb +++ b/test/test_utils.rb @@ -49,7 +49,9 @@ end end -TESTS_DIR = File.expand_path(File.dirname(__FILE__)).untaint +tests_dir = File.expand_path(File.dirname(__FILE__)) +tests_dir.untaint if RUBY_VERSION < '2.7' +TESTS_DIR = tests_dir TZINFO_TEST_ZONEINFO_DIR = File.join(TESTS_DIR, 'zoneinfo') unless defined? TZINFO_TEST_DATA_DIR @@ -286,11 +288,11 @@ def without_warnings # Runs a test with safe mode enabled ($SAFE = 1). def safe_test(options = {}) - # JRuby, Rubinius and TruffleRuby don't support SAFE levels. - available = !%w(jruby rbx truffleruby).include?(RUBY_ENGINE) + # Ruby >= 2.7, JRuby, Rubinius and TruffleRuby don't support SAFE levels. + available = RUBY_VERSION < '2.7' && !%w(jruby rbx truffleruby).include?(RUBY_ENGINE) if !available && options[:unavailable] == :skip - skip('JRuby, Rubinius and TruffleRuby don\'t support SAFE levels') + skip('Ruby >= 2.7, JRuby, Rubinius and TruffleRuby don\'t support SAFE levels') end thread = Thread.new do @@ -482,6 +484,17 @@ def assert_equal_with_offset_and_class(expected, actual) assert_equal(expected.class, actual.class) end end + + # Taint is deprecated in Ruby 2.7 and outputs a warning. Silence the warning. + if RUBY_VERSION >= '2.7' + module TaintExt + refine Object do + def taint + self + end + end + end + end end TestUtils.prepare_test_zoneinfo_dir diff --git a/test/ts_all_zoneinfo.rb b/test/ts_all_zoneinfo.rb index 9fe2a261..870a0618 100644 --- a/test/ts_all_zoneinfo.rb +++ b/test/ts_all_zoneinfo.rb @@ -7,6 +7,8 @@ # Use a zoneinfo directory containing files needed by the tests. # The symlinks in this directory are set up in test_utils.rb. -TZInfo::DataSource.set(:zoneinfo, File.join(File.expand_path(File.dirname(__FILE__)), 'zoneinfo').untaint) +zoneinfo_path = File.join(File.expand_path(File.dirname(__FILE__)), 'zoneinfo') +zoneinfo_path.untaint if RUBY_VERSION < '2.7' +TZInfo::DataSource.set(:zoneinfo, zoneinfo_path) require_relative 'ts_all' diff --git a/test/tzinfo-data2/tzinfo/data.rb b/test/tzinfo-data2/tzinfo/data.rb index ed808aa1..972cc8db 100644 --- a/test/tzinfo-data2/tzinfo/data.rb +++ b/test/tzinfo-data2/tzinfo/data.rb @@ -4,8 +4,11 @@ module TZInfo # Top level module for TZInfo::Data. module Data + location = File.dirname(File.dirname(__FILE__)) + location.untaint if RUBY_VERSION < '2.7' + # The directory containing the TZInfo::Data files. - LOCATION = File.dirname(File.dirname(__FILE__)).untaint.freeze + LOCATION = location.freeze end end diff --git a/tzinfo.gemspec b/tzinfo.gemspec index 503687de..364f256d 100644 --- a/tzinfo.gemspec +++ b/tzinfo.gemspec @@ -1,4 +1,4 @@ -require File.join(File.expand_path(File.dirname(__FILE__)), 'lib', 'tzinfo', 'version').untaint +require File.join(File.expand_path(File.dirname(__FILE__)), 'lib', 'tzinfo', 'version') Gem::Specification.new do |s| s.name = 'tzinfo'