class Gem::Package
Attributes
Checksums for the contents of the package
Permission for other files
Permission for directories
The files in this package. This is not the contents of the gem, just the files in the top-level container.
Reference to the gem being packaged.
Permission for program files
The security policy used for verifying the contents of this package.
Sets the Gem::Specification
to use to build this package.
Public Class Methods
# File rubygems/package.rb, line 128 def self.build(spec, skip_validation = false, strict_validation = false, file_name = nil) gem_file = file_name || spec.file_name package = new gem_file package.spec = spec package.build skip_validation, strict_validation gem_file end
Creates a new Gem::Package for the file at gem
. gem
can also be provided as an IO object.
If gem
is an existing file in the old format a Gem::Package::Old
will be returned.
# File rubygems/package.rb, line 145 def self.new(gem, security_policy = nil) gem = if gem.is_a?(Gem::Package::Source) gem elsif gem.respond_to? :read Gem::Package::IOSource.new gem else Gem::Package::FileSource.new gem end return super unless Gem::Package == self return super unless gem.present? return super unless gem.start return super unless gem.start.include? 'MD5SUM =' Gem::Package::Old.new gem end
Extracts the Gem::Specification
and raw metadata from the .gem file at path
.
# File rubygems/package.rb, line 168 def self.raw_spec(path, security_policy = nil) format = new(path, security_policy) spec = format.spec metadata = nil File.open path, Gem.binary_mode do |io| tar = Gem::Package::TarReader.new io tar.each_entry do |entry| case entry.full_name when 'metadata' then metadata = entry.read when 'metadata.gz' then metadata = Gem::Util.gunzip entry.read end end end return spec, metadata end
Public Instance Methods
Adds a checksum for each entry in the gem to checksums.yaml.gz.
# File rubygems/package.rb, line 216 def add_checksums(tar) Gem.load_yaml checksums_by_algorithm = Hash.new { |h, algorithm| h[algorithm] = {} } @checksums.each do |name, digests| digests.each do |algorithm, digest| checksums_by_algorithm[algorithm][name] = digest.hexdigest end end tar.add_file_signed 'checksums.yaml.gz', 0444, @signer do |io| gzip_to io do |gz_io| YAML.dump checksums_by_algorithm, gz_io end end end
Builds this package based on the specification set by spec=
# File rubygems/package.rb, line 294 def build(skip_validation = false, strict_validation = false) raise ArgumentError, "skip_validation = true and strict_validation = true are incompatible" if skip_validation && strict_validation Gem.load_yaml @spec.mark_version @spec.validate true, strict_validation unless skip_validation setup_signer( signer_options: { expiration_length_days: Gem.configuration.cert_expiration_length_days } ) @gem.with_write_io do |gem_io| Gem::Package::TarWriter.new gem_io do |gem| add_metadata gem add_contents gem add_checksums gem end end say <<-EOM Successfully built RubyGem Name: #{@spec.name} Version: #{@spec.version} File: #{File.basename @gem.path} EOM ensure @signer = nil end
A list of file names contained in this gem
# File rubygems/package.rb, line 329 def contents return @contents if @contents verify unless @spec @contents = [] @gem.with_read_io do |io| gem_tar = Gem::Package::TarReader.new io gem_tar.each do |entry| next unless entry.full_name == 'data.tar.gz' open_tar_gz entry do |pkg_tar| pkg_tar.each do |contents_entry| @contents << contents_entry.full_name end end return @contents end end end
Copies this package to path
(if possible)
# File rubygems/package.rb, line 209 def copy_to(path) FileUtils.cp @gem.path, path unless File.exist? path end
Extracts the files in this package into destination_dir
If pattern
is specified, only entries matching that glob will be extracted.
# File rubygems/package.rb, line 388 def extract_files(destination_dir, pattern = "*") verify unless @spec FileUtils.mkdir_p destination_dir, :mode => dir_mode && 0755 @gem.with_read_io do |io| reader = Gem::Package::TarReader.new io reader.each do |entry| next unless entry.full_name == 'data.tar.gz' extract_tar_gz entry, destination_dir, pattern return # ignore further entries end end end
Gzips content written to gz_io
to io
.
# File rubygems/package.rb, line 466 def gzip_to(io) # :yields: gz_io gz_io = Zlib::GzipWriter.new io, Zlib::BEST_COMPRESSION gz_io.mtime = @build_time yield gz_io ensure gz_io.close end
# File rubygems/package.rb, line 512 def mkdir_p_safe(mkdir, mkdir_options, destination_dir, file_name) destination_dir = File.realpath(File.expand_path(destination_dir)) parts = mkdir.split(File::SEPARATOR) parts.reduce do |path, basename| path = File.realpath(path) unless path == "" path = File.expand_path(path + File::SEPARATOR + basename) lstat = File.lstat path rescue nil if !lstat || !lstat.directory? unless normalize_path(path).start_with? normalize_path(destination_dir) and (FileUtils.mkdir path, **mkdir_options rescue false) raise Gem::Package::PathError.new(file_name, destination_dir) end end path end end
# File rubygems/package.rb, line 504 def normalize_path(pathname) if Gem.win_platform? pathname.downcase else pathname end end
Reads and loads checksums.yaml.gz from the tar file gem
# File rubygems/package.rb, line 556 def read_checksums(gem) Gem.load_yaml @checksums = gem.seek 'checksums.yaml.gz' do |entry| Zlib::GzipReader.wrap entry do |gz_io| Gem::SafeYAML.safe_load gz_io.read end end end
Prepares the gem for signing and checksum generation. If a signing certificate and key are not present only checksum generation is set up.
# File rubygems/package.rb, line 570 def setup_signer(signer_options: {}) passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE'] if @spec.signing_key @signer = Gem::Security::Signer.new( @spec.signing_key, @spec.cert_chain, passphrase, signer_options ) @spec.signing_key = nil @spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_s } else @signer = Gem::Security::Signer.new nil, nil, passphrase @spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_pem } if @signer.cert_chain end end
The spec for this gem.
If this is a package for a built gem the spec is loaded from the gem and returned. If this is a package for a gem being built the provided spec is returned.
# File rubygems/package.rb, line 597 def spec verify unless @spec @spec end
Verifies that this gem:
-
Contains a valid gem specification
-
Contains a contents archive
-
The contents archive is not corrupt
After verification the gem specification from the gem is available from spec
# File rubygems/package.rb, line 613 def verify @files = [] @spec = nil @gem.with_read_io do |io| Gem::Package::TarReader.new io do |reader| read_checksums reader verify_files reader end end verify_checksums @digests, @checksums @security_policy.verify_signatures @spec, @digests, @signatures if @security_policy true rescue Gem::Security::Exception @spec = nil @files = [] raise rescue Errno::ENOENT => e raise Gem::Package::FormatError.new e.message rescue Gem::Package::TarInvalidError => e raise Gem::Package::FormatError.new e.message, @gem end
Verifies entry
in a .gem file.
# File rubygems/package.rb, line 663 def verify_entry(entry) file_name = entry.full_name @files << file_name case file_name when /\.sig$/ then @signatures[$`] = entry.read if @security_policy return else digest entry end case file_name when "metadata", "metadata.gz" then load_spec entry when 'data.tar.gz' then verify_gz entry end rescue => e message = "package is corrupt, exception while verifying: " + "#{e.message} (#{e.class})" raise Gem::Package::FormatError.new message, @gem end
Verifies the files of the gem
# File rubygems/package.rb, line 690 def verify_files(gem) gem.each do |entry| verify_entry entry end unless @spec raise Gem::Package::FormatError.new 'package metadata is missing', @gem end unless @files.include? 'data.tar.gz' 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
Protected Instance Methods
Creates a new package that will read or write to the file gem
.
# File rubygems/package.rb, line 192 def initialize(gem, security_policy) # :notnew: @gem = gem @build_time = Gem.source_date_epoch @checksums = {} @contents = nil @digests = Hash.new { |h, algorithm| h[algorithm] = {} } @files = nil @security_policy = security_policy @signatures = {} @signer = nil @spec = nil end