diff options
Diffstat (limited to 'lib/rubygems/package.rb')
-rw-r--r-- | lib/rubygems/package.rb | 115 |
1 files changed, 69 insertions, 46 deletions
diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index 4672866985..9b2829c894 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true -#-- + +# rubocop:disable Style/AsciiComments + # Copyright (C) 2004 Mauricio Julio Fernández Pradier # See LICENSE.txt for additional licensing information. -#++ -require_relative "../rubygems" +# rubocop:enable Style/AsciiComments + require_relative "security" require_relative "user_interaction" @@ -56,9 +58,9 @@ class Gem::Package def initialize(message, source = nil) if source - @path = source.path + @path = source.is_a?(String) ? source : source.path - message = message + " in #{path}" if path + message += " in #{path}" if path end super message @@ -67,15 +69,13 @@ class Gem::Package class PathError < Error def initialize(destination, destination_dir) - super "installing into parent path %s of %s is not allowed" % - [destination, destination_dir] + super format("installing into parent path %s of %s is not allowed", destination, destination_dir) end end class SymlinkError < Error def initialize(name, destination, destination_dir) - super "installing symlink '%s' pointing to parent path %s of %s is not allowed" % - [name, destination, destination_dir] + super format("installing symlink '%s' pointing to parent path %s of %s is not allowed", name, destination, destination_dir) end end @@ -154,7 +154,7 @@ class Gem::Package Gem::Package::FileSource.new gem end - return super unless Gem::Package == self + return super unless self == Gem::Package return super unless gem.present? return super unless gem.start @@ -186,7 +186,7 @@ class Gem::Package end end - return spec, metadata + [spec, metadata] end ## @@ -229,7 +229,7 @@ class Gem::Package end end - tar.add_file_signed "checksums.yaml.gz", 0444, @signer do |io| + tar.add_file_signed "checksums.yaml.gz", 0o444, @signer do |io| gzip_to io do |gz_io| Psych.dump checksums_by_algorithm, gz_io end @@ -241,7 +241,7 @@ class Gem::Package # and adds this file to the +tar+. def add_contents(tar) # :nodoc: - digests = tar.add_file_signed "data.tar.gz", 0444, @signer do |io| + digests = tar.add_file_signed "data.tar.gz", 0o444, @signer do |io| gzip_to io do |gz_io| Gem::Package::TarWriter.new gz_io do |data_tar| add_files data_tar @@ -267,7 +267,7 @@ class Gem::Package tar.add_file_simple file, stat.mode, stat.size do |dst_io| File.open file, "rb" do |src_io| - dst_io.write src_io.read 16384 until src_io.eof? + copy_stream(src_io, dst_io) end end end @@ -277,7 +277,7 @@ class Gem::Package # Adds the package's Gem::Specification to the +tar+ file def add_metadata(tar) # :nodoc: - digests = tar.add_file_signed "metadata.gz", 0444, @signer do |io| + digests = tar.add_file_signed "metadata.gz", 0o444, @signer do |io| gzip_to io do |gz_io| gz_io.write @spec.to_yaml end @@ -346,6 +346,8 @@ EOM return @contents end end + rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e + raise Gem::Package::FormatError.new e.message, @gem end ## @@ -354,18 +356,21 @@ EOM def digest(entry) # :nodoc: algorithms = if @checksums - @checksums.keys - else - [Gem::Security::DIGEST_NAME].compact + @checksums.to_h {|algorithm, _| [algorithm, Gem::Security.create_digest(algorithm)] } + elsif Gem::Security::DIGEST_NAME + { Gem::Security::DIGEST_NAME => Gem::Security.create_digest(Gem::Security::DIGEST_NAME) } end - algorithms.each do |algorithm| - digester = Gem::Security.create_digest(algorithm) - - digester << entry.read(16384) until entry.eof? + return @digests if algorithms.nil? || algorithms.empty? - entry.rewind + buf = String.new(capacity: 16_384, encoding: Encoding::BINARY) + until entry.eof? + entry.readpartial(16_384, buf) + algorithms.each_value {|digester| digester << buf } + end + entry.rewind + algorithms.each do |algorithm, digester| @digests[algorithm][entry.full_name] = digester end @@ -381,7 +386,7 @@ EOM def extract_files(destination_dir, pattern = "*") verify unless @spec - FileUtils.mkdir_p destination_dir, :mode => dir_mode && 0755 + FileUtils.mkdir_p destination_dir, mode: dir_mode && 0o755 @gem.with_read_io do |io| reader = Gem::Package::TarReader.new io @@ -391,9 +396,11 @@ EOM extract_tar_gz entry, destination_dir, pattern - return # ignore further entries + break # ignore further entries end end + rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e + raise Gem::Package::FormatError.new e.message, @gem end ## @@ -408,6 +415,8 @@ EOM # extracted. def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc: + destination_dir = File.realpath(destination_dir) + directories = [] symlinks = [] @@ -430,8 +439,6 @@ EOM FileUtils.rm_rf destination - mkdir_options = {} - mkdir_options[:mode] = dir_mode ? 0755 : (entry.header.mode if entry.directory?) mkdir = if entry.directory? destination @@ -440,13 +447,13 @@ EOM end unless directories.include?(mkdir) - FileUtils.mkdir_p mkdir, **mkdir_options + FileUtils.mkdir_p mkdir, mode: dir_mode ? 0o755 : (entry.header.mode if entry.directory?) directories << mkdir end if entry.file? - File.open(destination, "wb") {|out| out.write entry.read } - FileUtils.chmod file_mode(entry.header.mode), destination + File.open(destination, "wb") {|out| copy_stream(entry, out) } + FileUtils.chmod file_mode(entry.header.mode) & ~File.umask, destination end verbose destination @@ -467,7 +474,7 @@ EOM end def file_mode(mode) # :nodoc: - ((mode & 0111).zero? ? data_mode : prog_mode) || + ((mode & 0o111).zero? ? data_mode : prog_mode) || # If we're not using one of the default modes, then we're going to fall # back to the mode from the tarball. In this case we need to mask it down # to fit into 2^16 bits (the maximum value for a mode in CRuby since it @@ -505,7 +512,6 @@ EOM raise Gem::Package::PathError.new(destination, destination_dir) unless normalize_path(destination).start_with? normalize_path(destination_dir + "/") - destination.tap(&Gem::UNTAINT) destination end @@ -521,12 +527,13 @@ EOM # Loads a Gem::Specification from the TarEntry +entry+ def load_spec(entry) # :nodoc: + limit = 10 * 1024 * 1024 case entry.full_name when "metadata" then - @spec = Gem::Specification.from_yaml entry.read + @spec = Gem::Specification.from_yaml limit_read(entry, "metadata", limit) when "metadata.gz" then Zlib::GzipReader.wrap(entry, external_encoding: Encoding::UTF_8) do |gzio| - @spec = Gem::Specification.from_yaml gzio.read + @spec = Gem::Specification.from_yaml limit_read(gzio, "metadata.gz", limit) end end end @@ -550,7 +557,7 @@ EOM @checksums = gem.seek "checksums.yaml.gz" do |entry| Zlib::GzipReader.wrap entry do |gz_io| - Gem::SafeYAML.safe_load gz_io.read + Gem::SafeYAML.safe_load limit_read(gz_io, "checksums.yaml.gz", 10 * 1024 * 1024) end end end @@ -571,10 +578,10 @@ EOM ) @spec.signing_key = nil - @spec.cert_chain = @signer.cert_chain.map {|cert| cert.to_s } + @spec.cert_chain = @signer.cert_chain.map(&:to_s) else @signer = Gem::Security::Signer.new nil, nil, passphrase - @spec.cert_chain = @signer.cert_chain.map {|cert| cert.to_pem } if + @spec.cert_chain = @signer.cert_chain.map(&:to_pem) if @signer.cert_chain end end @@ -616,8 +623,7 @@ EOM verify_checksums @digests, @checksums - @security_policy.verify_signatures @spec, @digests, @signatures if - @security_policy + @security_policy&.verify_signatures @spec, @digests, @signatures true rescue Gem::Security::Exception @@ -626,7 +632,7 @@ EOM raise rescue Errno::ENOENT => e raise Gem::Package::FormatError.new e.message - rescue Gem::Package::TarInvalidError => e + rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e raise Gem::Package::FormatError.new e.message, @gem end @@ -658,7 +664,7 @@ EOM case file_name when /\.sig$/ then - @signatures[$`] = entry.read if @security_policy + @signatures[$`] = limit_read(entry, file_name, 1024 * 1024) if @security_policy return else digest entry @@ -670,7 +676,7 @@ EOM when "data.tar.gz" then verify_gz entry end - rescue + rescue StandardError warn "Exception while verifying #{@gem.path}" raise end @@ -689,11 +695,11 @@ EOM unless @files.include? "data.tar.gz" raise Gem::Package::FormatError.new \ - "package content (data.tar.gz) is missing", @gem + "package content (data.tar.gz) is missing", @gem end - if (duplicates = @files.group_by {|f| f }.select {|k,v| v.size > 1 }.map(&:first)) && duplicates.any? - raise Gem::Security::Exception, "duplicate files in the package: (#{duplicates.map(&:inspect).join(', ')})" + if (duplicates = @files.group_by {|f| f }.select {|_k,v| v.size > 1 }.map(&:first)) && duplicates.any? + raise Gem::Security::Exception, "duplicate files in the package: (#{duplicates.map(&:inspect).join(", ")})" end end @@ -702,11 +708,28 @@ EOM def verify_gz(entry) # :nodoc: Zlib::GzipReader.wrap entry do |gzio| - gzio.read 16384 until gzio.eof? # gzip checksum verification + # TODO: read into a buffer once zlib supports it + gzio.read 16_384 until gzio.eof? # gzip checksum verification end rescue Zlib::GzipFile::Error => e raise Gem::Package::FormatError.new(e.message, entry.full_name) end + + if RUBY_ENGINE == "truffleruby" + def copy_stream(src, dst) # :nodoc: + dst.write src.read + end + else + def copy_stream(src, dst) # :nodoc: + IO.copy_stream(src, dst) + end + end + + def limit_read(io, name, limit) + bytes = io.read(limit + 1) + raise Gem::Package::FormatError, "#{name} is too big (over #{limit} bytes)" if bytes.size > limit + bytes + end end require_relative "package/digest_io" |