diff --git a/ruby.spec b/ruby.spec index 32222d1..cfd0407 100644 --- a/ruby.spec +++ b/ruby.spec @@ -29,7 +29,7 @@ %global rubygems_dir %{_datadir}/rubygems # Bundled libraries versions -%global rubygems_version 2.7.3 +%global rubygems_version 2.7.6 %global molinillo_version 0.5.7 # TODO: The IRB has strange versioning. Keep the Ruby's versioning ATM. @@ -145,6 +145,10 @@ Patch12: ruby-2.5.0-Disable-Tokyo-TZ-tests.patch # Fix thread_safe tests suite segfaults. # https://bugs.ruby-lang.org/issues/14357 Patch13: ruby-2.5.0-st.c-retry-operations-if-rebuilt.patch +# Fix: Multiple vulnerabilities in RubyGems +# https://bugzilla.redhat.com/show_bug.cgi?id=1547431 +# https://www.ruby-lang.org/en/news/2018/02/17/multiple-vulnerabilities-in-rubygems/ +Patch14: rubygems-2.5.0-multiple-vulnerabilities.patch Requires: %{name}-libs%{?_isa} = %{version}-%{release} Suggests: rubypick @@ -532,6 +536,7 @@ rm -rf ext/fiddle/libffi* %patch11 -p1 %patch12 -p1 %patch13 -p1 +%patch14 -p0 # Provide an example of usage of the tapset: cp -a %{SOURCE3} . @@ -1072,6 +1077,11 @@ make check TESTS="-v $DISABLE_TESTS" %{gem_dir}/specifications/xmlrpc-%{xmlrpc_version}.gemspec %changelog +* Wed Feb 23 2018 Pavel Valena - 2.5.0-89 +- Fix: Multiple vulnerabilities in RubyGems + https://bugzilla.redhat.com/show_bug.cgi?id=1547431 + https://www.ruby-lang.org/en/news/2018/02/17/multiple-vulnerabilities-in-rubygems/ + * Tue Feb 13 2018 Vít Ondruch - 2.5.0-89 - Drop obsolete ldconfig scriptlets. - Add GMP dependency. diff --git a/rubygems-2.5.0-multiple-vulnerabilities.patch b/rubygems-2.5.0-multiple-vulnerabilities.patch new file mode 100644 index 0000000..e32926a --- /dev/null +++ b/rubygems-2.5.0-multiple-vulnerabilities.patch @@ -0,0 +1,2349 @@ +diff --git lib/rubygems.rb lib/rubygems.rb +index 0475ced164..2762bfcb88 100644 +--- lib/rubygems.rb ++++ lib/rubygems.rb +@@ -10,7 +10,7 @@ + require 'thread' + + module Gem +- VERSION = "2.7.3" ++ VERSION = "2.7.6" + end + + # Must be first since it unloads the prelude from 1.9.2 +@@ -161,7 +161,7 @@ module Gem + # these are defined in Ruby 1.8.7, hence the need for this convoluted setup. + + READ_BINARY_ERRORS = begin +- read_binary_errors = [Errno::EACCES, Errno::EROFS] ++ read_binary_errors = [Errno::EACCES, Errno::EROFS, Errno::ENOSYS] + read_binary_errors << Errno::ENOTSUP if Errno.const_defined?(:ENOTSUP) + read_binary_errors + end.freeze +@@ -171,7 +171,7 @@ module Gem + # these are defined in Ruby 1.8.7. + + WRITE_BINARY_ERRORS = begin +- write_binary_errors = [] ++ write_binary_errors = [Errno::ENOSYS] + write_binary_errors << Errno::ENOTSUP if Errno.const_defined?(:ENOTSUP) + write_binary_errors + end.freeze +@@ -871,19 +871,19 @@ def self.refresh + # Safely read a file in binary mode on all platforms. + + def self.read_binary(path) +- open path, 'rb+' do |f| ++ File.open path, 'rb+' do |f| + f.flock(File::LOCK_EX) + f.read + end + rescue *READ_BINARY_ERRORS +- open path, 'rb' do |f| ++ File.open path, 'rb' do |f| + f.read + end + rescue Errno::ENOLCK # NFS + if Thread.main != Thread.current + raise + else +- open path, 'rb' do |f| ++ File.open path, 'rb' do |f| + f.read + end + end +diff --git lib/rubygems/commands/generate_index_command.rb lib/rubygems/commands/generate_index_command.rb +index 01f1f88405..0b677b73a9 100644 +--- lib/rubygems/commands/generate_index_command.rb ++++ lib/rubygems/commands/generate_index_command.rb +@@ -68,7 +68,7 @@ def execute + + if not File.exist?(options[:directory]) or + not File.directory?(options[:directory]) then +- alert_error "unknown directory name #{directory}." ++ alert_error "unknown directory name #{options[:directory]}." + terminate_interaction 1 + else + indexer = Gem::Indexer.new options.delete(:directory), options +diff --git lib/rubygems/commands/owner_command.rb lib/rubygems/commands/owner_command.rb +index 8e2271657a..637b5bdc4d 100644 +--- lib/rubygems/commands/owner_command.rb ++++ lib/rubygems/commands/owner_command.rb +@@ -64,7 +64,7 @@ def show_owners name + end + + with_response response do |resp| +- owners = YAML.load resp.body ++ owners = Gem::SafeYAML.load resp.body + + say "Owners for gem: #{name}" + owners.each do |owner| +diff --git lib/rubygems/commands/setup_command.rb lib/rubygems/commands/setup_command.rb +index 5d1414d102..6966cde01a 100644 +--- lib/rubygems/commands/setup_command.rb ++++ lib/rubygems/commands/setup_command.rb +@@ -350,7 +350,9 @@ def fake_spec.full_gem_path + def install_default_bundler_gem + return unless Gem::USE_BUNDLER_FOR_GEMDEPS + +- mkdir_p Gem::Specification.default_specifications_dir ++ specs_dir = Gem::Specification.default_specifications_dir ++ File.join(options[:destdir], specs_dir) unless Gem.win_platform? ++ mkdir_p specs_dir + + # Workaround for non-git environment. + gemspec = File.open('bundler/bundler.gemspec', 'rb'){|f| f.read.gsub(/`git ls-files -z`/, "''") } +@@ -359,23 +361,36 @@ def install_default_bundler_gem + bundler_spec = Gem::Specification.load("bundler/bundler.gemspec") + bundler_spec.files = Dir.chdir("bundler") { Dir["{*.md,{lib,exe,man}/**/*}"] } + bundler_spec.executables -= %w[bundler bundle_ruby] +- Dir.entries(Gem::Specification.default_specifications_dir). ++ ++ # Remove bundler-*.gemspec in default specification directory. ++ Dir.entries(specs_dir). + select {|gs| gs.start_with?("bundler-") }. +- each {|gs| File.delete(File.join(Gem::Specification.default_specifications_dir, gs)) } ++ each {|gs| File.delete(File.join(specs_dir, gs)) } + +- default_spec_path = File.join(Gem::Specification.default_specifications_dir, "#{bundler_spec.full_name}.gemspec") ++ default_spec_path = File.join(specs_dir, "#{bundler_spec.full_name}.gemspec") + Gem.write_binary(default_spec_path, bundler_spec.to_ruby) + + bundler_spec = Gem::Specification.load(default_spec_path) + ++ # Remove gemspec that was same version of vendored bundler. ++ normal_gemspec = File.join(Gem.default_dir, "specifications", "bundler-#{bundler_spec.version}.gemspec") ++ if File.file? normal_gemspec ++ File.delete normal_gemspec ++ end ++ ++ # Remove gem files that were same version of vendored bundler. + if File.directory? bundler_spec.gems_dir + Dir.entries(bundler_spec.gems_dir). +- select {|default_gem| File.basename(default_gem).match(/^bundler-#{Gem::Version::VERSION_PATTERN}$/) }. ++ select {|default_gem| File.basename(default_gem) == "bundler-#{bundler_spec.version}" }. + each {|default_gem| rm_r File.join(bundler_spec.gems_dir, default_gem) } + end + +- mkdir_p bundler_spec.bin_dir +- bundler_spec.executables.each {|e| cp File.join("bundler", bundler_spec.bindir, e), File.join(bundler_spec.bin_dir, e) } ++ bundler_bin_dir = File.join(Gem.default_dir, 'gems', bundler_spec.full_name, bundler_spec.bindir) ++ File.join(options[:destdir], bundler_bin_dir) unless Gem.win_platform? ++ mkdir_p bundler_bin_dir ++ bundler_spec.executables.each do |e| ++ cp File.join("bundler", bundler_spec.bindir, e), File.join(bundler_bin_dir, e) ++ end + + if Gem.win_platform? + require 'rubygems/installer' +diff --git lib/rubygems/commands/unpack_command.rb lib/rubygems/commands/unpack_command.rb +index eb7f550673..b873f20d28 100644 +--- lib/rubygems/commands/unpack_command.rb ++++ lib/rubygems/commands/unpack_command.rb +@@ -94,7 +94,7 @@ def execute + + spec_file = File.basename spec.spec_file + +- open spec_file, 'w' do |io| ++ File.open spec_file, 'w' do |io| + io.write metadata + end + else +@@ -176,7 +176,7 @@ def get_metadata path, security_policy = nil + + metadata = nil + +- open path, Gem.binary_mode do |io| ++ File.open path, Gem.binary_mode do |io| + tar = Gem::Package::TarReader.new io + tar.each_entry do |entry| + case entry.full_name +diff --git lib/rubygems/config_file.rb lib/rubygems/config_file.rb +index a4efed0f5a..c0d19dbfc2 100644 +--- lib/rubygems/config_file.rb ++++ lib/rubygems/config_file.rb +@@ -458,7 +458,7 @@ def to_yaml # :nodoc: + + # Writes out this config file, replacing its source. + def write +- open config_file_name, 'w' do |io| ++ File.open config_file_name, 'w' do |io| + io.write to_yaml + end + end +diff --git lib/rubygems/ext/builder.rb lib/rubygems/ext/builder.rb +index a1619c97d7..eb9db199d5 100644 +--- lib/rubygems/ext/builder.rb ++++ lib/rubygems/ext/builder.rb +@@ -212,7 +212,7 @@ def write_gem_make_out output # :nodoc: + + FileUtils.mkdir_p @spec.extension_dir + +- open destination, 'wb' do |io| io.puts output end ++ File.open destination, 'wb' do |io| io.puts output end + + destination + end +diff --git lib/rubygems/indexer.rb lib/rubygems/indexer.rb +index 871cc09d8d..3ea994414b 100644 +--- lib/rubygems/indexer.rb ++++ lib/rubygems/indexer.rb +@@ -2,6 +2,7 @@ + require 'rubygems' + require 'rubygems/package' + require 'time' ++require 'tmpdir' + + begin + gem 'builder' +@@ -64,7 +65,7 @@ def initialize(directory, options = {}) + @build_modern = options[:build_modern] + + @dest_directory = directory +- @directory = File.join(Dir.tmpdir, "gem_generate_index_#{$$}") ++ @directory = Dir.mktmpdir 'gem_generate_index' + + marshal_name = "Marshal.#{Gem.marshal_version}" + +@@ -123,7 +124,7 @@ def build_marshal_gemspecs specs + marshal_name = File.join @quick_marshal_dir, spec_file_name + + marshal_zipped = Gem.deflate Marshal.dump(spec) +- open marshal_name, 'wb' do |io| io.write marshal_zipped end ++ File.open marshal_name, 'wb' do |io| io.write marshal_zipped end + + files << marshal_name + +@@ -261,7 +262,7 @@ def compress(filename, extension) + + zipped = Gem.deflate data + +- open "#{filename}.#{extension}", 'wb' do |io| ++ File.open "#{filename}.#{extension}", 'wb' do |io| + io.write zipped + end + end +@@ -427,7 +428,7 @@ def update_specs_index(index, source, dest) + + specs_index = compact_specs specs_index.uniq.sort + +- open dest, 'wb' do |io| ++ File.open dest, 'wb' do |io| + Marshal.dump specs_index, io + end + end +diff --git lib/rubygems/installer.rb lib/rubygems/installer.rb +index 0cbca0791b..ee5fedeb64 100644 +--- lib/rubygems/installer.rb ++++ lib/rubygems/installer.rb +@@ -206,7 +206,7 @@ def check_executable_overwrite filename # :nodoc: + ruby_executable = false + existing = nil + +- open generated_bin, 'rb' do |io| ++ File.open generated_bin, 'rb' do |io| + next unless io.gets =~ /^#!/ # shebang + io.gets # blankline + +@@ -427,7 +427,7 @@ def default_spec_file + # specifications directory. + + def write_spec +- open spec_file, 'w' do |file| ++ File.open spec_file, 'w' do |file| + spec.installed_by_version = Gem.rubygems_version + + file.puts spec.to_ruby_for_cache +@@ -464,7 +464,12 @@ def generate_windows_script(filename, bindir) + def generate_bin # :nodoc: + return if spec.executables.nil? or spec.executables.empty? + +- Dir.mkdir @bin_dir unless File.exist? @bin_dir ++ begin ++ Dir.mkdir @bin_dir ++ rescue SystemCallError ++ raise unless File.directory? @bin_dir ++ end ++ + raise Gem::FilePermissionError.new(@bin_dir) unless File.writable? @bin_dir + + spec.executables.each do |filename| +@@ -863,7 +868,7 @@ def write_build_info_file + + build_info_file = File.join build_info_dir, "#{spec.full_name}.info" + +- open build_info_file, 'w' do |io| ++ File.open build_info_file, 'w' do |io| + @build_args.each do |arg| + io.puts arg + end +diff --git lib/rubygems/package.rb lib/rubygems/package.rb +index 77811ed5ec..b924122827 100644 +--- lib/rubygems/package.rb ++++ lib/rubygems/package.rb +@@ -219,7 +219,7 @@ def add_files tar # :nodoc: + next unless stat.file? + + tar.add_file_simple file, stat.mode, stat.size do |dst_io| +- open file, 'rb' do |src_io| ++ File.open file, 'rb' do |src_io| + dst_io.write src_io.read 16384 until src_io.eof? + end + end +@@ -378,9 +378,9 @@ def extract_tar_gz io, destination_dir, pattern = "*" # :nodoc: + File.dirname destination + end + +- FileUtils.mkdir_p mkdir, mkdir_options ++ mkdir_p_safe mkdir, mkdir_options, destination_dir, entry.full_name + +- open destination, 'wb' do |out| ++ File.open destination, 'wb' do |out| + out.write entry.read + FileUtils.chmod entry.header.mode, destination + end if entry.file? +@@ -416,20 +416,35 @@ def install_location filename, destination_dir # :nodoc: + raise Gem::Package::PathError.new(filename, destination_dir) if + filename.start_with? '/' + +- destination_dir = File.realpath destination_dir if +- File.respond_to? :realpath ++ destination_dir = realpath destination_dir + destination_dir = File.expand_path destination_dir + + destination = File.join destination_dir, filename + destination = File.expand_path destination + + raise Gem::Package::PathError.new(destination, destination_dir) unless +- destination.start_with? destination_dir ++ destination.start_with? destination_dir + '/' + + destination.untaint + destination + end + ++ def mkdir_p_safe mkdir, mkdir_options, destination_dir, file_name ++ destination_dir = realpath File.expand_path(destination_dir) ++ parts = mkdir.split(File::SEPARATOR) ++ parts.reduce do |path, basename| ++ path = realpath path unless path == "" ++ path = File.expand_path(path + File::SEPARATOR + basename) ++ lstat = File.lstat path rescue nil ++ if !lstat || !lstat.directory? ++ unless path.start_with? destination_dir and (FileUtils.mkdir path, mkdir_options rescue false) ++ raise Gem::Package::PathError.new(file_name, destination_dir) ++ end ++ end ++ path ++ end ++ end ++ + ## + # Loads a Gem::Specification from the TarEntry +entry+ + +@@ -603,6 +618,10 @@ def verify_files gem + raise Gem::Package::FormatError.new \ + 'package content (data.tar.gz) is missing', @gem + end ++ ++ if duplicates = @files.group_by {|f| f }.select {|k,v| v.size > 1 }.map(&:first) and duplicates.any? ++ raise Gem::Security::Exception, "duplicate files in the package: (#{duplicates.map(&:inspect).join(', ')})" ++ end + end + + ## +@@ -616,6 +635,16 @@ def verify_gz entry # :nodoc: + raise Gem::Package::FormatError.new(e.message, entry.full_name) + end + ++ if File.respond_to? :realpath ++ def realpath file ++ File.realpath file ++ end ++ else ++ def realpath file ++ file ++ end ++ end ++ + end + + require 'rubygems/package/digest_io' +diff --git lib/rubygems/package/file_source.rb lib/rubygems/package/file_source.rb +index 1a4dc4c824..ecc3a68677 100644 +--- lib/rubygems/package/file_source.rb ++++ lib/rubygems/package/file_source.rb +@@ -23,11 +23,11 @@ def present? + end + + def with_write_io &block +- open path, 'wb', &block ++ File.open path, 'wb', &block + end + + def with_read_io &block +- open path, 'rb', &block ++ File.open path, 'rb', &block + end + + end +diff --git lib/rubygems/package/old.rb lib/rubygems/package/old.rb +index f6e6e67c38..322d682ca8 100644 +--- lib/rubygems/package/old.rb ++++ lib/rubygems/package/old.rb +@@ -80,7 +80,7 @@ def extract_files destination_dir + + FileUtils.mkdir_p File.dirname destination + +- open destination, 'wb', entry['mode'] do |out| ++ File.open destination, 'wb', entry['mode'] do |out| + out.write file_data + end + +diff --git lib/rubygems/package/tar_header.rb lib/rubygems/package/tar_header.rb +index c54bd14d57..d557357114 100644 +--- lib/rubygems/package/tar_header.rb ++++ lib/rubygems/package/tar_header.rb +@@ -104,25 +104,30 @@ def self.from(stream) + fields = header.unpack UNPACK_FORMAT + + new :name => fields.shift, +- :mode => fields.shift.oct, +- :uid => fields.shift.oct, +- :gid => fields.shift.oct, +- :size => fields.shift.oct, +- :mtime => fields.shift.oct, +- :checksum => fields.shift.oct, ++ :mode => strict_oct(fields.shift), ++ :uid => strict_oct(fields.shift), ++ :gid => strict_oct(fields.shift), ++ :size => strict_oct(fields.shift), ++ :mtime => strict_oct(fields.shift), ++ :checksum => strict_oct(fields.shift), + :typeflag => fields.shift, + :linkname => fields.shift, + :magic => fields.shift, +- :version => fields.shift.oct, ++ :version => strict_oct(fields.shift), + :uname => fields.shift, + :gname => fields.shift, +- :devmajor => fields.shift.oct, +- :devminor => fields.shift.oct, ++ :devmajor => strict_oct(fields.shift), ++ :devminor => strict_oct(fields.shift), + :prefix => fields.shift, + + :empty => empty + end + ++ def self.strict_oct(str) ++ return str.oct if str =~ /\A[0-7]*\z/ ++ raise ArgumentError, "#{str.inspect} is not an octal string" ++ end ++ + ## + # Creates a new TarHeader using +vals+ + +diff --git lib/rubygems/package/tar_writer.rb lib/rubygems/package/tar_writer.rb +index f68b8d4c5e..390f7851a3 100644 +--- lib/rubygems/package/tar_writer.rb ++++ lib/rubygems/package/tar_writer.rb +@@ -196,6 +196,8 @@ def add_file_signed name, mode, signer + digest_name == signer.digest_name + end + ++ raise "no #{signer.digest_name} in #{digests.values.compact}" unless signature_digest ++ + if signer.key then + signature = signer.sign signature_digest.digest + +diff --git lib/rubygems/request_set/lockfile.rb lib/rubygems/request_set/lockfile.rb +index 7f6eadb939..76ad17d486 100644 +--- lib/rubygems/request_set/lockfile.rb ++++ lib/rubygems/request_set/lockfile.rb +@@ -223,7 +223,7 @@ def to_s + def write + content = to_s + +- open "#{@gem_deps_file}.lock", 'w' do |io| ++ File.open "#{@gem_deps_file}.lock", 'w' do |io| + io.write content + end + end +diff --git lib/rubygems/security.rb lib/rubygems/security.rb +index 4690dd9230..236577c5a3 100644 +--- lib/rubygems/security.rb ++++ lib/rubygems/security.rb +@@ -578,7 +578,7 @@ def self.trusted_certificates &block + def self.write pemmable, path, permissions = 0600, passphrase = nil, cipher = KEY_CIPHER + path = File.expand_path path + +- open path, 'wb', permissions do |io| ++ File.open path, 'wb', permissions do |io| + if passphrase and cipher + io.write pemmable.to_pem cipher, passphrase + else +diff --git lib/rubygems/security/trust_dir.rb lib/rubygems/security/trust_dir.rb +index bf44975cc6..849cf3cd3e 100644 +--- lib/rubygems/security/trust_dir.rb ++++ lib/rubygems/security/trust_dir.rb +@@ -93,7 +93,7 @@ def trust_cert certificate + + destination = cert_path certificate + +- open destination, 'wb', @permissions[:trusted_cert] do |io| ++ File.open destination, 'wb', @permissions[:trusted_cert] do |io| + io.write certificate.to_pem + end + end +diff --git lib/rubygems/server.rb lib/rubygems/server.rb +index 93b3af36f8..62c3dfe9cf 100644 +--- lib/rubygems/server.rb ++++ lib/rubygems/server.rb +@@ -623,6 +623,18 @@ def root(req, res) + executables = nil if executables.empty? + executables.last["is_last"] = true if executables + ++ # Pre-process spec homepage for safety reasons ++ begin ++ homepage_uri = URI.parse(spec.homepage) ++ if [URI::HTTP, URI::HTTPS].member? homepage_uri.class ++ homepage_uri = spec.homepage ++ else ++ homepage_uri = "." ++ end ++ rescue URI::InvalidURIError ++ homepage_uri = "." ++ end ++ + specs << { + "authors" => spec.authors.sort.join(", "), + "date" => spec.date.to_s, +@@ -632,7 +644,7 @@ def root(req, res) + "only_one_executable" => (executables && executables.size == 1), + "full_name" => spec.full_name, + "has_deps" => !deps.empty?, +- "homepage" => spec.homepage, ++ "homepage" => homepage_uri, + "name" => spec.name, + "rdoc_installed" => Gem::RDoc.new(spec).rdoc_installed?, + "ri_installed" => Gem::RDoc.new(spec).ri_installed?, +diff --git lib/rubygems/source.rb lib/rubygems/source.rb +index bd84c217a7..b28b850660 100644 +--- lib/rubygems/source.rb ++++ lib/rubygems/source.rb +@@ -160,7 +160,7 @@ def fetch_spec name_tuple + if update_cache? then + FileUtils.mkdir_p cache_dir + +- open local_spec, 'wb' do |io| ++ File.open local_spec, 'wb' do |io| + io.write spec + end + end +diff --git lib/rubygems/specification.rb lib/rubygems/specification.rb +index efc08c4738..2560324b7a 100644 +--- lib/rubygems/specification.rb ++++ lib/rubygems/specification.rb +@@ -15,6 +15,7 @@ + require 'rubygems/stub_specification' + require 'rubygems/util/list' + require 'stringio' ++require 'uri' + + ## + # The Specification class contains the information for a Gem. Typically +@@ -2822,10 +2823,16 @@ def validate packaging = true + raise Gem::InvalidSpecificationException, "#{lazy} is not a summary" + end + +- if homepage and not homepage.empty? and +- homepage !~ /\A[a-z][a-z\d+.-]*:/i then +- raise Gem::InvalidSpecificationException, +- "\"#{homepage}\" is not a URI" ++ # Make sure a homepage is valid HTTP/HTTPS URI ++ if homepage and not homepage.empty? ++ begin ++ homepage_uri = URI.parse(homepage) ++ unless [URI::HTTP, URI::HTTPS].member? homepage_uri.class ++ raise Gem::InvalidSpecificationException, "\"#{homepage}\" is not a valid HTTP URI" ++ end ++ rescue URI::InvalidURIError ++ raise Gem::InvalidSpecificationException, "\"#{homepage}\" is not a valid HTTP URI" ++ end + end + + # Warnings +diff --git lib/rubygems/stub_specification.rb lib/rubygems/stub_specification.rb +index 8337375ab4..ae2effbc84 100644 +--- lib/rubygems/stub_specification.rb ++++ lib/rubygems/stub_specification.rb +@@ -113,6 +113,8 @@ def data + unless @data + begin + saved_lineno = $. ++ ++ # TODO It should be use `File.open`, but bundler-1.16.1 example expects Kernel#open. + open loaded_from, OPEN_MODE do |file| + begin + file.readline # discard encoding line +diff --git lib/rubygems/test_case.rb lib/rubygems/test_case.rb +index f7f216e5e3..39aa4fc9a7 100644 +--- lib/rubygems/test_case.rb ++++ lib/rubygems/test_case.rb +@@ -488,7 +488,7 @@ def git_gem name = 'a', version = 1 + + gemspec = "#{name}.gemspec" + +- open File.join(directory, gemspec), 'w' do |io| ++ File.open File.join(directory, gemspec), 'w' do |io| + io.write git_spec.to_ruby + end + +@@ -592,7 +592,7 @@ def mu_pp(obj) + # Reads a Marshal file at +path+ + + def read_cache(path) +- open path.dup.untaint, 'rb' do |io| ++ File.open path.dup.untaint, 'rb' do |io| + Marshal.load io.read + end + end +@@ -612,7 +612,7 @@ def write_file(path) + dir = File.dirname path + FileUtils.mkdir_p dir unless File.directory? dir + +- open path, 'wb' do |io| ++ File.open path, 'wb' do |io| + yield io if block_given? + end + +@@ -727,7 +727,7 @@ def install_default_gems(*specs) + install_default_specs(*specs) + + specs.each do |spec| +- open spec.loaded_from, 'w' do |io| ++ File.open spec.loaded_from, 'w' do |io| + io.write spec.to_ruby_for_cache + end + end +@@ -1363,7 +1363,7 @@ def save_gemspec name = 'a', version = 1, directory = '.' + yield specification if block_given? + end + +- open File.join(directory, "#{name}.gemspec"), 'w' do |io| ++ File.open File.join(directory, "#{name}.gemspec"), 'w' do |io| + io.write vendor_spec.to_ruby + end + +diff --git lib/rubygems/test_utilities.rb lib/rubygems/test_utilities.rb +index 686916ea02..83c9d2d0fe 100644 +--- lib/rubygems/test_utilities.rb ++++ lib/rubygems/test_utilities.rb +@@ -346,7 +346,7 @@ def spec name, version, dependencies = nil, &block + end + + def write_spec spec # :nodoc: +- open spec.spec_file, 'w' do |io| ++ File.open spec.spec_file, 'w' do |io| + io.write spec.to_ruby_for_cache + end + end +diff --git lib/rubygems/util.rb lib/rubygems/util.rb +index 2de45c900b..6c75910004 100644 +--- lib/rubygems/util.rb ++++ lib/rubygems/util.rb +@@ -114,7 +114,8 @@ def self.traverse_parents directory, &block + + here = File.expand_path directory + loop do +- Dir.chdir here, &block ++ Dir.chdir here, &block rescue Errno::EACCES ++ + new_here = File.expand_path('..', here) + return if new_here == here # toplevel + here = new_here +diff --git lib/rubygems/validator.rb lib/rubygems/validator.rb +index 83448229bb..6842e4fa9c 100644 +--- lib/rubygems/validator.rb ++++ lib/rubygems/validator.rb +@@ -34,7 +34,7 @@ def verify_gem(gem_data) + # gem_path:: [String] Path to gem file + + def verify_gem_file(gem_path) +- open gem_path, Gem.binary_mode do |file| ++ File.open gem_path, Gem.binary_mode do |file| + gem_data = file.read + verify_gem gem_data + end +@@ -109,7 +109,7 @@ def alien(gems=[]) + + good, gone, unreadable = nil, nil, nil, nil + +- open gem_path, Gem.binary_mode do |file| ++ File.open gem_path, Gem.binary_mode do |file| + package = Gem::Package.new gem_path + + good, gone = package.contents.partition { |file_name| +@@ -134,7 +134,7 @@ def alien(gems=[]) + + source = File.join gem_directory, entry['path'] + +- open source, Gem.binary_mode do |f| ++ File.open source, Gem.binary_mode do |f| + unless f.read == data then + errors[gem_name][entry['path']] = "Modified from original" + end +diff --git test/rubygems/test_gem.rb test/rubygems/test_gem.rb +index 8a11cc2ecf..183771f0f3 100644 +--- test/rubygems/test_gem.rb ++++ test/rubygems/test_gem.rb +@@ -7,7 +7,7 @@ + require 'tmpdir' + + # TODO: push this up to test_case.rb once battle tested +-$SAFE=1 ++ + $LOAD_PATH.map! do |path| + path.dup.untaint + end +@@ -463,7 +463,7 @@ def test_self_ensure_gem_directories_missing_parents + assert File.directory?(util_cache_dir) + end + +- unless win_platform? then # only for FS that support write protection ++ unless win_platform? || Process.uid.zero? then # only for FS that support write protection + def test_self_ensure_gem_directories_write_protected + gemdir = File.join @tempdir, "egd" + FileUtils.rm_r gemdir rescue nil +@@ -775,7 +775,7 @@ def test_self_prefix_sitelibdir + end + + def test_self_read_binary +- open 'test', 'w' do |io| ++ File.open 'test', 'w' do |io| + io.write "\xCF\x80" + end + +@@ -1643,7 +1643,7 @@ def test_use_gemdeps + spec = Gem::Specification.find { |s| s == spec } + refute spec.activated? + +- open gem_deps_file, 'w' do |io| ++ File.open gem_deps_file, 'w' do |io| + io.write 'gem "a"' + end + +@@ -1662,7 +1662,7 @@ def test_use_gemdeps_ENV + + refute spec.activated? + +- open 'gem.deps.rb', 'w' do |io| ++ File.open 'gem.deps.rb', 'w' do |io| + io.write 'gem "a"' + end + +@@ -1706,7 +1706,7 @@ def test_use_gemdeps_automatic + + refute spec.activated? + +- open 'Gemfile', 'w' do |io| ++ File.open 'Gemfile', 'w' do |io| + io.write 'gem "a"' + end + +@@ -1735,7 +1735,7 @@ def test_use_gemdeps_disabled + + refute spec.activated? + +- open 'gem.deps.rb', 'w' do |io| ++ File.open 'gem.deps.rb', 'w' do |io| + io.write 'gem "a"' + end + +@@ -1750,7 +1750,7 @@ def test_use_gemdeps_missing_gem + skip 'Insecure operation - read' if RUBY_VERSION <= "1.8.7" + rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], 'x' + +- open 'x', 'w' do |io| ++ File.open 'x', 'w' do |io| + io.write 'gem "a"' + end + +@@ -1791,7 +1791,7 @@ def test_use_gemdeps_specific + spec = Gem::Specification.find { |s| s == spec } + refute spec.activated? + +- open 'x', 'w' do |io| ++ File.open 'x', 'w' do |io| + io.write 'gem "a"' + end + +diff --git test/rubygems/test_gem_commands_cleanup_command.rb test/rubygems/test_gem_commands_cleanup_command.rb +index c55e195975..60d208fcc0 100644 +--- test/rubygems/test_gem_commands_cleanup_command.rb ++++ test/rubygems/test_gem_commands_cleanup_command.rb +@@ -158,7 +158,7 @@ def test_execute_all_user_no_sudo + assert_path_exists @a_1_1.gem_dir + ensure + FileUtils.chmod 0755, @gemhome +- end unless win_platform? ++ end unless win_platform? || Process.uid.zero? + + def test_execute_dry_run + @cmd.options[:args] = %w[a] +diff --git test/rubygems/test_gem_commands_install_command.rb test/rubygems/test_gem_commands_install_command.rb +index dd86a85038..822d40e3f3 100644 +--- test/rubygems/test_gem_commands_install_command.rb ++++ test/rubygems/test_gem_commands_install_command.rb +@@ -131,6 +131,7 @@ def test_execute_local_transitive_prerelease + + def test_execute_no_user_install + skip 'skipped on MS Windows (chmod has no effect)' if win_platform? ++ skip 'skipped in root privilege' if Process.uid.zero? + + specs = spec_fetcher do |fetcher| + fetcher.gem 'a', 2 +diff --git test/rubygems/test_gem_commands_owner_command.rb test/rubygems/test_gem_commands_owner_command.rb +index 44652c1093..53cac4ce87 100644 +--- test/rubygems/test_gem_commands_owner_command.rb ++++ test/rubygems/test_gem_commands_owner_command.rb +@@ -43,6 +43,31 @@ def test_show_owners + assert_match %r{- 4}, @ui.output + end + ++ def test_show_owners_dont_load_objects ++ skip "testing a psych-only API" unless defined?(::Psych::DisallowedClass) ++ ++ response = < 0 +- assert Gem::Specification.find_all_by_name('x').length == 0 ++ assert Gem::Specification.find_all_by_name('x').length.zero? + end + + def test_execute_all +diff --git test/rubygems/test_gem_dependency_installer.rb test/rubygems/test_gem_dependency_installer.rb +index e55cc75682..3d76291668 100644 +--- test/rubygems/test_gem_dependency_installer.rb ++++ test/rubygems/test_gem_dependency_installer.rb +@@ -424,7 +424,7 @@ def test_install_dependency_existing_extension + extconf_rb = File.join @gemhome, 'gems', 'e-1', 'extconf.rb' + FileUtils.mkdir_p File.dirname extconf_rb + +- open extconf_rb, 'w' do |io| ++ File.open extconf_rb, 'w' do |io| + io.write <<-EXTCONF_RB + require 'mkmf' + create_makefile 'e' +diff --git test/rubygems/test_gem_doctor.rb test/rubygems/test_gem_doctor.rb +index 39b8a11692..8db65d70ce 100644 +--- test/rubygems/test_gem_doctor.rb ++++ test/rubygems/test_gem_doctor.rb +@@ -24,7 +24,7 @@ def test_doctor + + FileUtils.rm b.spec_file + +- open c.spec_file, 'w' do |io| ++ File.open c.spec_file, 'w' do |io| + io.write 'this will raise an exception when evaluated.' + end + +@@ -77,7 +77,7 @@ def test_doctor_dry_run + + FileUtils.rm b.spec_file + +- open c.spec_file, 'w' do |io| ++ File.open c.spec_file, 'w' do |io| + io.write 'this will raise an exception when evaluated.' + end + +diff --git test/rubygems/test_gem_ext_builder.rb test/rubygems/test_gem_ext_builder.rb +index d142ef28da..3dabd3e350 100644 +--- test/rubygems/test_gem_ext_builder.rb ++++ test/rubygems/test_gem_ext_builder.rb +@@ -32,7 +32,7 @@ def test_class_make + results = [] + + Dir.chdir @ext do +- open 'Makefile', 'w' do |io| ++ File.open 'Makefile', 'w' do |io| + io.puts <<-MAKEFILE + all: + \t@#{Gem.ruby} -e "puts %Q{all: \#{ENV['DESTDIR']}}" +@@ -72,7 +72,7 @@ def test_class_make_no_clean + results = [] + + Dir.chdir @ext do +- open 'Makefile', 'w' do |io| ++ File.open 'Makefile', 'w' do |io| + io.puts <<-MAKEFILE + all: + \t@#{Gem.ruby} -e "puts %Q{all: \#{ENV['DESTDIR']}}" +@@ -107,7 +107,7 @@ def test_build_extensions + + extconf_rb = File.join ext_dir, 'extconf.rb' + +- open extconf_rb, 'w' do |f| ++ File.open extconf_rb, 'w' do |f| + f.write <<-'RUBY' + require 'mkmf' + +@@ -168,7 +168,7 @@ def Gem.install_extension_in_lib + + extconf_rb = File.join ext_dir, 'extconf.rb' + +- open extconf_rb, 'w' do |f| ++ File.open extconf_rb, 'w' do |f| + f.write <<-'RUBY' + require 'mkmf' + +@@ -290,7 +290,7 @@ def test_build_extensions_with_build_args + + FileUtils.mkdir_p @spec.gem_dir + +- open File.join(@spec.gem_dir, "extconf.rb"), "w" do |f| ++ File.open File.join(@spec.gem_dir, "extconf.rb"), "w" do |f| + f.write <<-'RUBY' + puts "IN EXTCONF" + extconf_args = File.join File.dirname(__FILE__), 'extconf_args' +@@ -323,7 +323,7 @@ def test_initialize + + build_info_file = File.join build_info_dir, "#{@spec.full_name}.info" + +- open build_info_file, 'w' do |io| ++ File.open build_info_file, 'w' do |io| + io.puts '--with-foo-dir=/nonexistent' + end + +diff --git test/rubygems/test_gem_gem_runner.rb test/rubygems/test_gem_gem_runner.rb +index 0a1faa404a..d68ac4da81 100644 +--- test/rubygems/test_gem_gem_runner.rb ++++ test/rubygems/test_gem_gem_runner.rb +@@ -1,38 +1,6 @@ + # frozen_string_literal: true + require 'rubygems/test_case' +-begin +- gem_home_files = lambda{ +- if Dir.exist?(ENV["GEM_HOME"]) +- require "find" +- ary = Find.find(ENV["GEM_HOME"]).to_a +- else +- [] +- end +- } +- prev_gem_home = ENV["GEM_HOME"] +- prev_gem_home_files = gem_home_files.call +- prev_threads = Thread.list.map{|e| e.inspect} +- +- require 'rubygems/gem_runner' +-ensure +- if $! +- msg = < 'KEY' } + FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path + +- open Gem.configuration.credentials_path, 'w' do |f| ++ File.open Gem.configuration.credentials_path, 'w' do |f| + f.write keys.to_yaml + end + +@@ -59,7 +59,7 @@ def test_api_key_override + keys = { :rubygems_api_key => 'KEY', :other => 'OTHER' } + FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path + +- open Gem.configuration.credentials_path, 'w' do |f| ++ File.open Gem.configuration.credentials_path, 'w' do |f| + f.write keys.to_yaml + end + +@@ -163,7 +163,7 @@ def test_sign_in_with_other_credentials_doesnt_overwrite_other_keys + other_api_key = 'f46dbb18bb6a9c97cdc61b5b85c186a17403cdcbf' + + FileUtils.mkdir_p File.dirname(Gem.configuration.credentials_path) +- open Gem.configuration.credentials_path, 'w' do |f| ++ File.open Gem.configuration.credentials_path, 'w' do |f| + f.write Hash[:other_api_key, other_api_key].to_yaml + end + util_sign_in [api_key, 200, 'OK'] +diff --git test/rubygems/test_gem_indexer.rb test/rubygems/test_gem_indexer.rb +index a4a966e8de..5a9075e676 100644 +--- test/rubygems/test_gem_indexer.rb ++++ test/rubygems/test_gem_indexer.rb +@@ -39,8 +39,7 @@ def setup + + def test_initialize + assert_equal @tempdir, @indexer.dest_directory +- assert_equal File.join(Dir.tmpdir, "gem_generate_index_#{$$}"), +- @indexer.directory ++ assert_match %r{#{Dir.mktmpdir('gem_generate_index').match(/.*-/)}}, @indexer.directory + + indexer = Gem::Indexer.new @tempdir + assert indexer.build_modern +diff --git test/rubygems/test_gem_install_update_options.rb test/rubygems/test_gem_install_update_options.rb +index e2d546307d..371e408d27 100644 +--- test/rubygems/test_gem_install_update_options.rb ++++ test/rubygems/test_gem_install_update_options.rb +@@ -141,6 +141,8 @@ def test_user_install_enabled + def test_user_install_disabled_read_only + if win_platform? + skip('test_user_install_disabled_read_only test skipped on MS Windows') ++ elsif Process.uid.zero? ++ skip('test_user_install_disabled_read_only test skipped in root privilege') + else + @cmd.handle_options %w[--no-user-install] + +diff --git test/rubygems/test_gem_installer.rb test/rubygems/test_gem_installer.rb +index 39095c7dee..93b0482407 100644 +--- test/rubygems/test_gem_installer.rb ++++ test/rubygems/test_gem_installer.rb +@@ -140,7 +140,7 @@ def test_check_executable_overwrite_format_executable + s.require_path = 'lib' + end + +- open File.join(util_inst_bindir, 'executable'), 'w' do |io| ++ File.open File.join(util_inst_bindir, 'executable'), 'w' do |io| + io.write <<-EXEC + #!/usr/local/bin/ruby + # +@@ -437,6 +437,8 @@ def test_generate_bin_script_no_perms + + if win_platform? + skip('test_generate_bin_script_no_perms skipped on MS Windows') ++ elsif Process.uid.zero? ++ skip('test_generate_bin_script_no_perms skipped in root privilege') + else + FileUtils.chmod 0000, util_inst_bindir + +@@ -529,6 +531,8 @@ def test_generate_bin_symlink_no_perms + + if win_platform? + skip('test_generate_bin_symlink_no_perms skipped on MS Windows') ++ elsif Process.uid.zero? ++ skip('test_user_install_disabled_read_only test skipped in root privilege') + else + FileUtils.chmod 0000, util_inst_bindir + +diff --git test/rubygems/test_gem_package.rb test/rubygems/test_gem_package.rb +index cec1981c4c..d1664cf285 100644 +--- test/rubygems/test_gem_package.rb ++++ test/rubygems/test_gem_package.rb +@@ -24,7 +24,7 @@ def setup + end + + def test_class_new_old_format +- open 'old_format.gem', 'wb' do |io| ++ File.open 'old_format.gem', 'wb' do |io| + io.write SIMPLE_GEM + end + +@@ -45,7 +45,7 @@ def test_add_checksums + + FileUtils.mkdir 'lib' + +- open 'lib/code.rb', 'w' do |io| ++ File.open 'lib/code.rb', 'w' do |io| + io.write '# lib/code.rb' + end + +@@ -110,8 +110,8 @@ def test_add_files + + FileUtils.mkdir_p 'lib/empty' + +- open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end +- open 'lib/extra.rb', 'w' do |io| io.write '# lib/extra.rb' end ++ File.open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end ++ File.open 'lib/extra.rb', 'w' do |io| io.write '# lib/extra.rb' end + + package = Gem::Package.new 'bogus.gem' + package.spec = spec +@@ -140,7 +140,7 @@ def test_add_files_symlink + spec.files = %w[lib/code.rb lib/code_sym.rb] + + FileUtils.mkdir_p 'lib' +- open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end ++ File.open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end + + # NOTE: 'code.rb' is correct, because it's relative to lib/code_sym.rb + File.symlink('code.rb', 'lib/code_sym.rb') +@@ -179,7 +179,7 @@ def test_build + + FileUtils.mkdir 'lib' + +- open 'lib/code.rb', 'w' do |io| ++ File.open 'lib/code.rb', 'w' do |io| + io.write '# lib/code.rb' + end + +@@ -218,7 +218,7 @@ def test_build_auto_signed + + FileUtils.mkdir 'lib' + +- open 'lib/code.rb', 'w' do |io| ++ File.open 'lib/code.rb', 'w' do |io| + io.write '# lib/code.rb' + end + +@@ -261,7 +261,7 @@ def test_build_auto_signed_encrypted_key + + FileUtils.mkdir 'lib' + +- open 'lib/code.rb', 'w' do |io| ++ File.open 'lib/code.rb', 'w' do |io| + io.write '# lib/code.rb' + end + +@@ -311,7 +311,7 @@ def test_build_signed + + FileUtils.mkdir 'lib' + +- open 'lib/code.rb', 'w' do |io| ++ File.open 'lib/code.rb', 'w' do |io| + io.write '# lib/code.rb' + end + +@@ -348,7 +348,7 @@ def test_build_signed_encrypted_key + + FileUtils.mkdir 'lib' + +- open 'lib/code.rb', 'w' do |io| ++ File.open 'lib/code.rb', 'w' do |io| + io.write '# lib/code.rb' + end + +@@ -408,7 +408,7 @@ def test_extract_files_empty + end + end + +- open 'empty.gem', 'wb' do |io| ++ File.open 'empty.gem', 'wb' do |io| + io.write gem.string + end + +@@ -455,6 +455,31 @@ def test_extract_tar_gz_symlink_relative_path + File.read(extracted) + end + ++ def test_extract_symlink_parent ++ skip 'symlink not supported' if Gem.win_platform? ++ ++ package = Gem::Package.new @gem ++ ++ tgz_io = util_tar_gz do |tar| ++ tar.mkdir 'lib', 0755 ++ tar.add_symlink 'lib/link', '../..', 0644 ++ tar.add_file 'lib/link/outside.txt', 0644 do |io| io.write 'hi' end ++ end ++ ++ # Extract into a subdirectory of @destination; if this test fails it writes ++ # a file outside destination_subdir, but we want the file to remain inside ++ # @destination so it will be cleaned up. ++ destination_subdir = File.join @destination, 'subdir' ++ FileUtils.mkdir_p destination_subdir ++ ++ e = assert_raises Gem::Package::PathError do ++ package.extract_tar_gz tgz_io, destination_subdir ++ end ++ ++ assert_equal("installing into parent path lib/link/outside.txt of " + ++ "#{destination_subdir} is not allowed", e.message) ++ end ++ + def test_extract_tar_gz_directory + package = Gem::Package.new @gem + +@@ -566,6 +591,21 @@ def test_install_location_relative + "#{@destination} is not allowed", e.message) + end + ++ def test_install_location_suffix ++ package = Gem::Package.new @gem ++ ++ filename = "../#{File.basename(@destination)}suffix.rb" ++ ++ e = assert_raises Gem::Package::PathError do ++ package.install_location filename, @destination ++ end ++ ++ parent = File.expand_path File.join @destination, filename ++ ++ assert_equal("installing into parent path #{parent} of " + ++ "#{@destination} is not allowed", e.message) ++ end ++ + def test_load_spec + entry = StringIO.new Gem.gzip @spec.to_yaml + def entry.full_name() 'metadata.gz' end +@@ -620,7 +660,7 @@ def test_verify_checksum_bad + end + end + +- open 'mismatch.gem', 'wb' do |io| ++ File.open 'mismatch.gem', 'wb' do |io| + io.write gem.string + end + +@@ -670,7 +710,7 @@ def test_verify_checksum_missing + end + end + +- open 'data_checksum_missing.gem', 'wb' do |io| ++ File.open 'data_checksum_missing.gem', 'wb' do |io| + io.write gem.string + end + +@@ -723,6 +763,32 @@ def test_verify_nonexistent + assert_match %r%nonexistent.gem$%, e.message + end + ++ def test_verify_duplicate_file ++ FileUtils.mkdir_p 'lib' ++ FileUtils.touch 'lib/code.rb' ++ ++ build = Gem::Package.new @gem ++ build.spec = @spec ++ build.setup_signer ++ open @gem, 'wb' do |gem_io| ++ Gem::Package::TarWriter.new gem_io do |gem| ++ build.add_metadata gem ++ build.add_contents gem ++ ++ gem.add_file_simple 'a.sig', 0444, 0 ++ gem.add_file_simple 'a.sig', 0444, 0 ++ end ++ end ++ ++ package = Gem::Package.new @gem ++ ++ e = assert_raises Gem::Security::Exception do ++ package.verify ++ end ++ ++ assert_equal 'duplicate files in the package: ("a.sig")', e.message ++ end ++ + def test_verify_security_policy + skip 'openssl is missing' unless defined?(OpenSSL::SSL) + +@@ -773,14 +839,20 @@ def test_verify_security_policy_checksum_missing + FileUtils.mkdir 'lib' + FileUtils.touch 'lib/code.rb' + +- open @gem, 'wb' do |gem_io| ++ File.open @gem, 'wb' do |gem_io| + Gem::Package::TarWriter.new gem_io do |gem| + build.add_metadata gem + build.add_contents gem + + # write bogus data.tar.gz to foil signature + bogus_data = Gem.gzip 'hello' +- gem.add_file_simple 'data.tar.gz', 0444, bogus_data.length do |io| ++ fake_signer = Class.new do ++ def digest_name; 'SHA512'; end ++ def digest_algorithm; Digest(:SHA512); end ++ def key; 'key'; end ++ def sign(*); 'fake_sig'; end ++ end ++ gem.add_file_signed 'data2.tar.gz', 0444, fake_signer.new do |io| + io.write bogus_data + end + +@@ -804,7 +876,7 @@ def test_verify_security_policy_checksum_missing + end + + def test_verify_truncate +- open 'bad.gem', 'wb' do |io| ++ File.open 'bad.gem', 'wb' do |io| + io.write File.read(@gem, 1024) # don't care about newlines + end + +diff --git test/rubygems/test_gem_package_old.rb test/rubygems/test_gem_package_old.rb +index c15475b0c7..604981b3c1 100644 +--- test/rubygems/test_gem_package_old.rb ++++ test/rubygems/test_gem_package_old.rb +@@ -7,7 +7,7 @@ class TestGemPackageOld < Gem::TestCase + def setup + super + +- open 'old_format.gem', 'wb' do |io| ++ File.open 'old_format.gem', 'wb' do |io| + io.write SIMPLE_GEM + end + +diff --git test/rubygems/test_gem_package_tar_header.rb test/rubygems/test_gem_package_tar_header.rb +index d33877057d..43f508df45 100644 +--- test/rubygems/test_gem_package_tar_header.rb ++++ test/rubygems/test_gem_package_tar_header.rb +@@ -143,5 +143,26 @@ def test_update_checksum + assert_equal '012467', @tar_header.checksum + end + ++ def test_from_bad_octal ++ test_cases = [ ++ "00000006,44\000", # bogus character ++ "00000006789\000", # non-octal digit ++ "+0000001234\000", # positive sign ++ "-0000001000\000", # negative sign ++ "0x000123abc\000", # radix prefix ++ ] ++ ++ test_cases.each do |val| ++ header_s = @tar_header.to_s ++ # overwrite the size field ++ header_s[124, 12] = val ++ io = TempIO.new header_s ++ assert_raises ArgumentError do ++ new_header = Gem::Package::TarHeader.from io ++ end ++ io.close! if io.respond_to? :close! ++ end ++ end ++ + end + +diff --git test/rubygems/test_gem_rdoc.rb test/rubygems/test_gem_rdoc.rb +index 76ca8c45a9..0355883cb3 100644 +--- test/rubygems/test_gem_rdoc.rb ++++ test/rubygems/test_gem_rdoc.rb +@@ -223,6 +223,7 @@ def test_remove + + def test_remove_unwritable + skip 'chmod not supported' if Gem.win_platform? ++ skip 'skipped in root privilege' if Process.uid.zero? + FileUtils.mkdir_p @a.base_dir + FileUtils.chmod 0, @a.base_dir + +@@ -251,6 +252,7 @@ def test_setup + + def test_setup_unwritable + skip 'chmod not supported' if Gem.win_platform? ++ skip 'skipped in root privilege' if Process.uid.zero? + FileUtils.mkdir_p @a.doc_dir + FileUtils.chmod 0, @a.doc_dir + +diff --git test/rubygems/test_gem_remote_fetcher.rb test/rubygems/test_gem_remote_fetcher.rb +index ee5ac77717..20e34e84e1 100644 +--- test/rubygems/test_gem_remote_fetcher.rb ++++ test/rubygems/test_gem_remote_fetcher.rb +@@ -431,7 +431,7 @@ def test_download_install_dir + assert File.exist?(a1_cache_gem) + end + +- unless win_platform? # File.chmod doesn't work ++ unless win_platform? || Process.uid.zero? # File.chmod doesn't work + def test_download_local_read_only + FileUtils.mv @a1_gem, @tempdir + local_path = File.join @tempdir, @a1.file_name +diff --git test/rubygems/test_gem_request_set.rb test/rubygems/test_gem_request_set.rb +index 3a48827481..5dc6c1518d 100644 +--- test/rubygems/test_gem_request_set.rb ++++ test/rubygems/test_gem_request_set.rb +@@ -52,7 +52,7 @@ def test_install_from_gemdeps + rs = Gem::RequestSet.new + installed = [] + +- open 'gem.deps.rb', 'w' do |io| ++ File.open 'gem.deps.rb', 'w' do |io| + io.puts 'gem "a"' + io.flush + +@@ -78,7 +78,7 @@ def test_install_from_gemdeps_explain + + rs = Gem::RequestSet.new + +- open 'gem.deps.rb', 'w' do |io| ++ File.open 'gem.deps.rb', 'w' do |io| + io.puts 'gem "a"' + io.flush + +@@ -104,7 +104,7 @@ def test_install_from_gemdeps_install_dir + rs = Gem::RequestSet.new + installed = [] + +- open 'gem.deps.rb', 'w' do |io| ++ File.open 'gem.deps.rb', 'w' do |io| + io.puts 'gem "a"' + end + +@@ -128,7 +128,7 @@ def test_install_from_gemdeps_local + + rs = Gem::RequestSet.new + +- open 'gem.deps.rb', 'w' do |io| ++ File.open 'gem.deps.rb', 'w' do |io| + io.puts 'gem "a"' + io.flush + +@@ -150,7 +150,7 @@ def test_install_from_gemdeps_lockfile + rs = Gem::RequestSet.new + installed = [] + +- open 'gem.deps.rb.lock', 'w' do |io| ++ File.open 'gem.deps.rb.lock', 'w' do |io| + io.puts <<-LOCKFILE + GEM + remote: #{@gem_repo} +@@ -167,7 +167,7 @@ def test_install_from_gemdeps_lockfile + LOCKFILE + end + +- open 'gem.deps.rb', 'w' do |io| ++ File.open 'gem.deps.rb', 'w' do |io| + io.puts 'gem "b"' + end + +@@ -190,7 +190,7 @@ def test_install_from_gemdeps_version_mismatch + rs = Gem::RequestSet.new + installed = [] + +- open 'gem.deps.rb', 'w' do |io| ++ File.open 'gem.deps.rb', 'w' do |io| + io.puts <<-GEM_DEPS + gem "a" + ruby "0" +diff --git test/rubygems/test_gem_request_set_lockfile.rb test/rubygems/test_gem_request_set_lockfile.rb +index 908f97303e..7460b7efad 100644 +--- test/rubygems/test_gem_request_set_lockfile.rb ++++ test/rubygems/test_gem_request_set_lockfile.rb +@@ -31,7 +31,7 @@ def lockfile + def write_lockfile lockfile + @lock_file = File.expand_path "#{@gem_deps_file}.lock" + +- open @lock_file, 'w' do |io| ++ File.open @lock_file, 'w' do |io| + io.write lockfile + end + end +@@ -387,7 +387,7 @@ def test_to_s_git + s.add_dependency 'c', '~> 1.0' + end + +- open 'b.gemspec', 'w' do |io| ++ File.open 'b.gemspec', 'w' do |io| + io.write b.to_ruby + end + +@@ -400,7 +400,7 @@ def test_to_s_git + Dir.chdir 'c' do + c = Gem::Specification.new 'c', 1 + +- open 'c.gemspec', 'w' do |io| ++ File.open 'c.gemspec', 'w' do |io| + io.write c.to_ruby + end + +@@ -455,7 +455,7 @@ def test_write_error + + gem_deps_lock_file = "#{@gem_deps_file}.lock" + +- open gem_deps_lock_file, 'w' do |io| ++ File.open gem_deps_lock_file, 'w' do |io| + io.write 'hello' + end + +diff --git test/rubygems/test_gem_request_set_lockfile_parser.rb test/rubygems/test_gem_request_set_lockfile_parser.rb +index 9946c522d9..f3517da43a 100644 +--- test/rubygems/test_gem_request_set_lockfile_parser.rb ++++ test/rubygems/test_gem_request_set_lockfile_parser.rb +@@ -536,7 +536,7 @@ def test_parse_missing + end + + def write_lockfile lockfile +- open @lock_file, 'w' do |io| ++ File.open @lock_file, 'w' do |io| + io.write lockfile + end + end +diff --git test/rubygems/test_gem_request_set_lockfile_tokenizer.rb test/rubygems/test_gem_request_set_lockfile_tokenizer.rb +index ab506a14e6..f4aba6d94a 100644 +--- test/rubygems/test_gem_request_set_lockfile_tokenizer.rb ++++ test/rubygems/test_gem_request_set_lockfile_tokenizer.rb +@@ -295,7 +295,7 @@ def test_unget + end + + def write_lockfile lockfile +- open @lock_file, 'w' do |io| ++ File.open @lock_file, 'w' do |io| + io.write lockfile + end + end +diff --git test/rubygems/test_gem_resolver_git_specification.rb test/rubygems/test_gem_resolver_git_specification.rb +index 9e8e2c5715..211757eb20 100644 +--- test/rubygems/test_gem_resolver_git_specification.rb ++++ test/rubygems/test_gem_resolver_git_specification.rb +@@ -70,7 +70,7 @@ def test_install_extension + Dir.chdir 'git/a' do + FileUtils.mkdir_p 'ext/lib' + +- open 'ext/extconf.rb', 'w' do |io| ++ File.open 'ext/extconf.rb', 'w' do |io| + io.puts 'require "mkmf"' + io.puts 'create_makefile "a"' + end +diff --git test/rubygems/test_gem_server.rb test/rubygems/test_gem_server.rb +index 6fe02e480f..a018e65512 100644 +--- test/rubygems/test_gem_server.rb ++++ test/rubygems/test_gem_server.rb +@@ -100,7 +100,7 @@ def test_latest_specs_gemdirs + specs_dir = File.join dir, 'specifications' + FileUtils.mkdir_p specs_dir + +- open File.join(specs_dir, spec.spec_name), 'w' do |io| ++ File.open File.join(specs_dir, spec.spec_name), 'w' do |io| + io.write spec.to_ruby + end + +@@ -198,7 +198,7 @@ def test_quick_gemdirs + + FileUtils.mkdir_p specs_dir + +- open File.join(specs_dir, spec.spec_name), 'w' do |io| ++ File.open File.join(specs_dir, spec.spec_name), 'w' do |io| + io.write spec.to_ruby + end + +@@ -339,7 +339,7 @@ def test_root_gemdirs + specs_dir = File.join dir, 'specifications' + FileUtils.mkdir_p specs_dir + +- open File.join(specs_dir, spec.spec_name), 'w' do |io| ++ File.open File.join(specs_dir, spec.spec_name), 'w' do |io| + io.write spec.to_ruby + end + +@@ -353,6 +353,171 @@ def test_root_gemdirs + assert_match 'z 9', @res.body + end + ++ ++ def test_xss_homepage_fix_289313 ++ data = StringIO.new "GET / HTTP/1.0\r\n\r\n" ++ dir = "#{@gemhome}2" ++ ++ spec = util_spec 'xsshomepagegem', 1 ++ spec.homepage = "javascript:confirm(document.domain)" ++ ++ specs_dir = File.join dir, 'specifications' ++ FileUtils.mkdir_p specs_dir ++ ++ open File.join(specs_dir, spec.spec_name), 'w' do |io| ++ io.write spec.to_ruby ++ end ++ ++ server = Gem::Server.new dir, process_based_port, false ++ ++ @req.parse data ++ ++ server.root @req, @res ++ ++ assert_equal 200, @res.status ++ assert_match 'xsshomepagegem 1', @res.body ++ ++ # This verifies that the homepage for this spec is not displayed and is set to ".", because it's not a ++ # valid HTTP/HTTPS URL and could be unsafe in an HTML context. We would prefer to throw an exception here, ++ # but spec.homepage is currently free form and not currently required to be a URL, this behavior may be ++ # validated in future versions of Gem::Specification. ++ # ++ # There are two variant we're checking here, one where rdoc is not present, and one where rdoc is present in the same regex: ++ # ++ # Variant #1 - rdoc not installed ++ # ++ # xsshomepagegem 1 ++ # ++ # ++ # [rdoc] ++ # ++ # ++ # ++ # [www] ++ # ++ # Variant #2 - rdoc installed ++ # ++ # xsshomepagegem 1 ++ # ++ # ++ # \[rdoc\]<\/a> ++ # ++ # ++ # ++ # [www] ++ regex_match = /xsshomepagegem 1<\/b>[\n\s]+(\[rdoc\]<\/span>|\[rdoc\]<\/a>)[\n\s]+\[www\]<\/a>/ ++ assert_match regex_match, @res.body ++ end ++ ++ def test_invalid_homepage ++ data = StringIO.new "GET / HTTP/1.0\r\n\r\n" ++ dir = "#{@gemhome}2" ++ ++ spec = util_spec 'invalidhomepagegem', 1 ++ spec.homepage = "notavalidhomepageurl" ++ ++ specs_dir = File.join dir, 'specifications' ++ FileUtils.mkdir_p specs_dir ++ ++ open File.join(specs_dir, spec.spec_name), 'w' do |io| ++ io.write spec.to_ruby ++ end ++ ++ server = Gem::Server.new dir, process_based_port, false ++ ++ @req.parse data ++ ++ server.root @req, @res ++ ++ assert_equal 200, @res.status ++ assert_match 'invalidhomepagegem 1', @res.body ++ ++ # This verifies that the homepage for this spec is not displayed and is set to ".", because it's not a ++ # valid HTTP/HTTPS URL and could be unsafe in an HTML context. We would prefer to throw an exception here, ++ # but spec.homepage is currently free form and not currently required to be a URL, this behavior may be ++ # validated in future versions of Gem::Specification. ++ # ++ # There are two variant we're checking here, one where rdoc is not present, and one where rdoc is present in the same regex: ++ # ++ # Variant #1 - rdoc not installed ++ # ++ # invalidhomepagegem 1 ++ # ++ # ++ # [rdoc] ++ # ++ # ++ # ++ # [www] ++ # ++ # Variant #2 - rdoc installed ++ # ++ # invalidhomepagegem 1 ++ # ++ # ++ # \[rdoc\]<\/a> ++ # ++ # ++ # ++ # [www] ++ regex_match = /invalidhomepagegem 1<\/b>[\n\s]+(\[rdoc\]<\/span>|\[rdoc\]<\/a>)[\n\s]+\[www\]<\/a>/ ++ assert_match regex_match, @res.body ++ end ++ ++ def test_valid_homepage_http ++ data = StringIO.new "GET / HTTP/1.0\r\n\r\n" ++ dir = "#{@gemhome}2" ++ ++ spec = util_spec 'validhomepagegemhttp', 1 ++ spec.homepage = "http://rubygems.org" ++ ++ specs_dir = File.join dir, 'specifications' ++ FileUtils.mkdir_p specs_dir ++ ++ open File.join(specs_dir, spec.spec_name), 'w' do |io| ++ io.write spec.to_ruby ++ end ++ ++ server = Gem::Server.new dir, process_based_port, false ++ ++ @req.parse data ++ ++ server.root @req, @res ++ ++ assert_equal 200, @res.status ++ assert_match 'validhomepagegemhttp 1', @res.body ++ ++ regex_match = /validhomepagegemhttp 1<\/b>[\n\s]+(\[rdoc\]<\/span>|\[rdoc\]<\/a>)[\n\s]+\[www\]<\/a>/ ++ assert_match regex_match, @res.body ++ end ++ ++ def test_valid_homepage_https ++ data = StringIO.new "GET / HTTP/1.0\r\n\r\n" ++ dir = "#{@gemhome}2" ++ ++ spec = util_spec 'validhomepagegemhttps', 1 ++ spec.homepage = "https://rubygems.org" ++ ++ specs_dir = File.join dir, 'specifications' ++ FileUtils.mkdir_p specs_dir ++ ++ open File.join(specs_dir, spec.spec_name), 'w' do |io| ++ io.write spec.to_ruby ++ end ++ ++ server = Gem::Server.new dir, process_based_port, false ++ ++ @req.parse data ++ ++ server.root @req, @res ++ ++ assert_equal 200, @res.status ++ assert_match 'validhomepagegemhttps 1', @res.body ++ ++ regex_match = /validhomepagegemhttps 1<\/b>[\n\s]+(\[rdoc\]<\/span>|\[rdoc\]<\/a>)[\n\s]+\[www\]<\/a>/ ++ assert_match regex_match, @res.body ++ end ++ + def test_specs + data = StringIO.new "GET /specs.#{Gem.marshal_version} HTTP/1.0\r\n\r\n" + @req.parse data +@@ -378,7 +543,7 @@ def test_specs_gemdirs + specs_dir = File.join dir, 'specifications' + FileUtils.mkdir_p specs_dir + +- open File.join(specs_dir, spec.spec_name), 'w' do |io| ++ File.open File.join(specs_dir, spec.spec_name), 'w' do |io| + io.write spec.to_ruby + end + +diff --git test/rubygems/test_gem_source.rb test/rubygems/test_gem_source.rb +index 4a93e222f8..8805a9b404 100644 +--- test/rubygems/test_gem_source.rb ++++ test/rubygems/test_gem_source.rb +@@ -110,7 +110,7 @@ def test_fetch_spec_cached + + cache_file = File.join cache_dir, a1.spec_name + +- open cache_file, 'wb' do |io| ++ File.open cache_file, 'wb' do |io| + Marshal.dump a1, io + end + +@@ -163,7 +163,7 @@ def test_load_specs_cached + + cache_file = File.join cache_dir, "latest_specs.#{Gem.marshal_version}" + +- open cache_file, 'wb' do |io| ++ File.open cache_file, 'wb' do |io| + Marshal.dump latest_specs, io + end + +@@ -187,7 +187,7 @@ def test_load_specs_cached_empty + + cache_file = File.join cache_dir, "latest_specs.#{Gem.marshal_version}" + +- open cache_file, 'wb' do |io| ++ File.open cache_file, 'wb' do |io| + # Setup invalid data in the cache: + io.write Marshal.dump(latest_specs)[0, 10] + end +diff --git test/rubygems/test_gem_source_git.rb test/rubygems/test_gem_source_git.rb +index 0e13a11e7e..8f5d3ee745 100644 +--- test/rubygems/test_gem_source_git.rb ++++ test/rubygems/test_gem_source_git.rb +@@ -229,7 +229,7 @@ def test_specs + Dir.chdir 'b' do + b = Gem::Specification.new 'b', 1 + +- open 'b.gemspec', 'w' do |io| ++ File.open 'b.gemspec', 'w' do |io| + io.write b.to_ruby + end + +diff --git test/rubygems/test_gem_specification.rb test/rubygems/test_gem_specification.rb +index bb6acbc7de..badb297eee 100644 +--- test/rubygems/test_gem_specification.rb ++++ test/rubygems/test_gem_specification.rb +@@ -922,7 +922,7 @@ def test_self_load + end + + def test_self_load_relative +- open 'a-2.gemspec', 'w' do |io| ++ File.open 'a-2.gemspec', 'w' do |io| + io.write @a2.to_ruby_for_cache + end + +@@ -948,6 +948,9 @@ def test_self_load_tainted + @a2.files.clear + + assert_equal @a2, spec ++ ++ ensure ++ $SAFE = 0 + end + + def test_self_load_escape_curly +@@ -1111,7 +1114,7 @@ def test_self_remove_spec + end + + def test_self_remove_spec_removed +- open @a1.spec_file, 'w' do |io| ++ File.open @a1.spec_file, 'w' do |io| + io.write @a1.to_ruby + end + +@@ -1363,13 +1366,13 @@ def test_build_args + + assert_empty @ext.build_args + +- open @ext.build_info_file, 'w' do |io| ++ File.open @ext.build_info_file, 'w' do |io| + io.puts + end + + assert_empty @ext.build_args + +- open @ext.build_info_file, 'w' do |io| ++ File.open @ext.build_info_file, 'w' do |io| + io.puts '--with-foo-dir=wherever' + end + +@@ -1385,9 +1388,9 @@ def test_build_extensions + extconf_rb = File.join @ext.gem_dir, @ext.extensions.first + FileUtils.mkdir_p File.dirname extconf_rb + +- open extconf_rb, 'w' do |f| ++ File.open extconf_rb, 'w' do |f| + f.write <<-'RUBY' +- open 'Makefile', 'w' do |f| ++ File.open 'Makefile', 'w' do |f| + f.puts "clean:\n\techo clean" + f.puts "default:\n\techo built" + f.puts "install:\n\techo installed" +@@ -1435,9 +1438,9 @@ def test_build_extensions_default_gem + extconf_rb = File.join spec.gem_dir, spec.extensions.first + FileUtils.mkdir_p File.dirname extconf_rb + +- open extconf_rb, 'w' do |f| ++ File.open extconf_rb, 'w' do |f| + f.write <<-'RUBY' +- open 'Makefile', 'w' do |f| ++ File.open 'Makefile', 'w' do |f| + f.puts "default:\n\techo built" + f.puts "install:\n\techo installed" + end +@@ -1461,6 +1464,7 @@ def test_build_extensions_error + + def test_build_extensions_extensions_dir_unwritable + skip 'chmod not supported' if Gem.win_platform? ++ skip 'skipped in root privilege' if Process.uid.zero? + + ext_spec + +@@ -1469,9 +1473,9 @@ def test_build_extensions_extensions_dir_unwritable + extconf_rb = File.join @ext.gem_dir, @ext.extensions.first + FileUtils.mkdir_p File.dirname extconf_rb + +- open extconf_rb, 'w' do |f| ++ File.open extconf_rb, 'w' do |f| + f.write <<-'RUBY' +- open 'Makefile', 'w' do |f| ++ File.open 'Makefile', 'w' do |f| + f.puts "clean:\n\techo clean" + f.puts "default:\n\techo built" + f.puts "install:\n\techo installed" +@@ -1486,7 +1490,7 @@ def test_build_extensions_extensions_dir_unwritable + @ext.build_extensions + refute_path_exists @ext.extension_dir + ensure +- unless ($DEBUG or win_platform?) then ++ unless ($DEBUG or win_platform? or Process.uid.zero?) then + FileUtils.chmod 0755, File.join(@ext.base_dir, 'extensions') + FileUtils.chmod 0755, @ext.base_dir + end +@@ -1502,9 +1506,9 @@ def test_build_extensions_no_extensions_dir_unwritable + extconf_rb = File.join @ext.gem_dir, @ext.extensions.first + FileUtils.mkdir_p File.dirname extconf_rb + +- open extconf_rb, 'w' do |f| ++ File.open extconf_rb, 'w' do |f| + f.write <<-'RUBY' +- open 'Makefile', 'w' do |f| ++ File.open 'Makefile', 'w' do |f| + f.puts "clean:\n\techo clean" + f.puts "default:\n\techo built" + f.puts "install:\n\techo installed" +@@ -1551,9 +1555,9 @@ def test_build_extensions_preview + extconf_rb = File.join @ext.gem_dir, @ext.extensions.first + FileUtils.mkdir_p File.dirname extconf_rb + +- open extconf_rb, 'w' do |f| ++ File.open extconf_rb, 'w' do |f| + f.write <<-'RUBY' +- open 'Makefile', 'w' do |f| ++ File.open 'Makefile', 'w' do |f| + f.puts "clean:\n\techo clean" + f.puts "default:\n\techo built" + f.puts "install:\n\techo installed" +@@ -2882,7 +2886,22 @@ def test_validate_homepage + @a1.validate + end + +- assert_equal '"over at my cool site" is not a URI', e.message ++ assert_equal '"over at my cool site" is not a valid HTTP URI', e.message ++ ++ @a1.homepage = 'ftp://rubygems.org' ++ ++ e = assert_raises Gem::InvalidSpecificationException do ++ @a1.validate ++ end ++ ++ assert_equal '"ftp://rubygems.org" is not a valid HTTP URI', e.message ++ ++ @a1.homepage = 'http://rubygems.org' ++ assert_equal true, @a1.validate ++ ++ @a1.homepage = 'https://rubygems.org' ++ assert_equal true, @a1.validate ++ + end + end + +@@ -3418,9 +3437,9 @@ def test_missing_extensions_eh + extconf_rb = File.join @ext.gem_dir, @ext.extensions.first + FileUtils.mkdir_p File.dirname extconf_rb + +- open extconf_rb, 'w' do |f| ++ File.open extconf_rb, 'w' do |f| + f.write <<-'RUBY' +- open 'Makefile', 'w' do |f| ++ File.open 'Makefile', 'w' do |f| + f.puts "clean:\n\techo clean" + f.puts "default:\n\techo built" + f.puts "install:\n\techo installed" +diff --git test/rubygems/test_gem_stub_specification.rb test/rubygems/test_gem_stub_specification.rb +index 43680265c7..f9a3a236c0 100644 +--- test/rubygems/test_gem_stub_specification.rb ++++ test/rubygems/test_gem_stub_specification.rb +@@ -127,9 +127,9 @@ def test_missing_extensions_eh + extconf_rb = File.join s.gem_dir, s.extensions.first + FileUtils.mkdir_p File.dirname extconf_rb + +- open extconf_rb, 'w' do |f| ++ File.open extconf_rb, 'w' do |f| + f.write <<-'RUBY' +- open 'Makefile', 'w' do |f| ++ File.open 'Makefile', 'w' do |f| + f.puts "clean:\n\techo clean" + f.puts "default:\n\techo built" + f.puts "install:\n\techo installed" +@@ -149,7 +149,7 @@ def test_missing_extensions_eh_default_gem + spec = new_default_spec 'default', 1 + spec.extensions << 'extconf.rb' + +- open spec.loaded_from, 'w' do |io| ++ File.open spec.loaded_from, 'w' do |io| + io.write spec.to_ruby_for_cache + end + +@@ -198,7 +198,7 @@ def test_to_spec_missing_extensions + + def stub_with_version + spec = File.join @gemhome, 'specifications', 'stub_e-2.gemspec' +- open spec, 'w' do |io| ++ File.open spec, 'w' do |io| + io.write <<-STUB + # -*- encoding: utf-8 -*- + # stub: stub_v 2 ruby lib +@@ -221,7 +221,7 @@ def stub_with_version + + def stub_without_version + spec = File.join @gemhome, 'specifications', 'stub-2.gemspec' +- open spec, 'w' do |io| ++ File.open spec, 'w' do |io| + io.write <<-STUB + # -*- encoding: utf-8 -*- + # stub: stub_v ruby lib +@@ -245,7 +245,7 @@ def stub_without_version + + def stub_with_extension + spec = File.join @gemhome, 'specifications', 'stub_e-2.gemspec' +- open spec, 'w' do |io| ++ File.open spec, 'w' do |io| + io.write <<-STUB + # -*- encoding: utf-8 -*- + # stub: stub_e 2 ruby lib +@@ -271,7 +271,7 @@ def stub_with_extension + + def stub_without_extension + spec = File.join @gemhome, 'specifications', 'stub-2.gemspec' +- open spec, 'w' do |io| ++ File.open spec, 'w' do |io| + io.write <<-STUB + # -*- encoding: utf-8 -*- + # stub: stub 2 ruby lib +diff --git test/rubygems/test_gem_util.rb test/rubygems/test_gem_util.rb +index b85db44d51..3b7887d931 100644 +--- test/rubygems/test_gem_util.rb ++++ test/rubygems/test_gem_util.rb +@@ -5,6 +5,7 @@ + class TestGemUtil < Gem::TestCase + + def test_class_popen ++ skip "MJIT executes process and it's caught by Process.wait(-1)" if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? + assert_equal "0\n", Gem::Util.popen(Gem.ruby, '-e', 'p 0') + + assert_raises Errno::ECHILD do +@@ -29,6 +30,30 @@ def test_traverse_parents + loop { break if enum.next.nil? } # exhaust the enumerator + end + ++ def test_traverse_parents_does_not_crash_on_permissions_error ++ skip 'skipped on MS Windows (chmod has no effect)' if win_platform? ++ ++ FileUtils.mkdir_p 'd/e/f' ++ # remove 'execute' permission from "e" directory and make it ++ # impossible to cd into it and its children ++ FileUtils.chmod(0666, 'd/e') ++ ++ paths = Gem::Util.traverse_parents('d/e/f').to_a ++ ++ assert_equal File.join(@tempdir, 'd'), paths[0] ++ assert_equal @tempdir, paths[1] ++ if File.respond_to?(:realpath) ++ assert_equal File.realpath(Dir.tmpdir), paths[2] ++ assert_equal File.realpath("..", Dir.tmpdir), paths[3] ++ elsif RUBY_PLATFORM !~ /darwin/ ++ assert_equal Dir.tmpdir, paths[2] ++ assert_equal '/', paths[3] ++ end ++ ensure ++ # restore default permissions, allow the directory to be removed ++ FileUtils.chmod(0775, 'd/e') unless win_platform? ++ end ++ + def test_linked_list_find + list = [1,2,3,4,5].inject(Gem::List.new(0)) { |m,o| + Gem::List.new o, m +diff --git test/rubygems/test_gem_version.rb test/rubygems/test_gem_version.rb +index 56c818663e..792ad5f084 100644 +--- test/rubygems/test_gem_version.rb ++++ test/rubygems/test_gem_version.rb +@@ -2,6 +2,8 @@ + require 'rubygems/test_case' + require "rubygems/version" + ++require "minitest/benchmark" ++ + class TestGemVersion < Gem::TestCase + + class V < ::Gem::Version +@@ -102,6 +104,15 @@ def test_initialize_invalid + end + end + ++ def bench_anchored_version_pattern ++ assert_performance_linear 0.5 do |count| ++ version_string = count.times.map {|i| "0" * i.succ }.join(".") << "." ++ version_string =~ Gem::Version::ANCHORED_VERSION_PATTERN ++ end ++ rescue RegexpError ++ skip "It fails to allocate the memory for regex pattern of Gem::Version::ANCHORED_VERSION_PATTERN" ++ end ++ + def test_empty_version + ["", " ", " "].each do |empty| + assert_equal "0", Gem::Version.new(empty).version +diff --git test/rubygems/test_require.rb test/rubygems/test_require.rb +index a846f46833..e292ce226d 100644 +--- test/rubygems/test_require.rb ++++ test/rubygems/test_require.rb +@@ -38,18 +38,6 @@ def assert_require(path) + assert require(path), "'#{path}' was already required" + end + +- def append_latch spec +- dir = spec.gem_dir +- Dir.chdir dir do +- spec.files.each do |file| +- File.open file, 'a' do |fp| +- fp.puts "FILE_ENTERED_LATCH.release" +- fp.puts "FILE_EXIT_LATCH.await" +- end +- end +- end +- end +- + # Providing -I on the commandline should always beat gems + def test_dash_i_beats_gems + a1 = new_spec "a", "1", {"b" => "= 1"}, "lib/test_gem_require_a.rb" +@@ -80,6 +68,17 @@ def test_dash_i_beats_gems + Object.send :remove_const, :HELLO if Object.const_defined? :HELLO + end + ++ def create_sync_thread ++ Thread.new do ++ begin ++ yield ++ ensure ++ FILE_ENTERED_LATCH.release ++ FILE_EXIT_LATCH.await ++ end ++ end ++ end ++ + def test_concurrent_require + skip 'deadlock' if /^1\.8\./ =~ RUBY_VERSION + +@@ -91,11 +90,8 @@ def test_concurrent_require + + install_specs a1, b1 + +- append_latch a1 +- append_latch b1 +- +- t1 = Thread.new { assert_require 'a' } +- t2 = Thread.new { assert_require 'b' } ++ t1 = create_sync_thread{ assert_require 'a' } ++ t2 = create_sync_thread{ assert_require 'b' } + + # wait until both files are waiting on the exit latch + FILE_ENTERED_LATCH.await +@@ -106,10 +102,8 @@ def test_concurrent_require + assert t1.join, "thread 1 should exit" + assert t2.join, "thread 2 should exit" + ensure +- return if $! # skipping +- +- Object.send :remove_const, :FILE_ENTERED_LATCH +- Object.send :remove_const, :FILE_EXIT_LATCH ++ Object.send :remove_const, :FILE_ENTERED_LATCH if Object.const_defined? :FILE_ENTERED_LATCH ++ Object.send :remove_const, :FILE_EXIT_LATCH if Object.const_defined? :FILE_EXIT_LATCH + end + + def test_require_is_not_lazy_with_exact_req