Class: Gem::Package
| Relationships & Source Files | |
| Namespace Children | |
|
Classes:
| |
|
Exceptions:
| |
| Extension / Inclusion / Inheritance Descendants | |
|
Subclasses:
|
|
| Super Chains via Extension / Inclusion / Inheritance | |
|
Instance Chain:
|
|
| Inherits: | Object |
| Defined in: | lib/rubygems/package.rb |
Overview
Example using a Package
Builds a #gem file given a Specification. A #gem file is a tarball
which contains a data.tar.gz, metadata.gz, checksums.yaml.gz and possibly
signatures.
require 'rubygems'
require 'rubygems/package'
spec = Gem::Specification.new do |s|
s.summary = "Ruby based make-like utility."
s.name = 'rake'
s.version = PKG_VERSION
s.requirements << 'none'
s.files = PKG_FILES
s.description = <<-EOF
Rake is a Make-like program implemented in Ruby. Tasks
and dependencies are specified in standard Ruby syntax.
EOF
end
Gem::Package.build spec
Reads a #gem file.
require 'rubygems'
require 'rubygems/package'
the_gem = Gem::Package.new(path_to_dot_gem)
the_gem.contents # get the files in the gem
the_gem.extract_files destination_directory # extract the gem into a directory
the_gem.spec # get the spec out of the gem
the_gem.verify # check the gem is OK (contains valid gem specification, contains a not corrupt contents archive)
#files are the files in the #gem tar file, not the Ruby files in the gem #extract_files and #contents automatically call #verify
Constant Summary
-
MINIMUM_RUBYGEMS_VERSION =
# File 'lib/rubygems/package.rb', line 137
The minimum RubyGems version that can install content-addressable gems. Built into
required_rubygems_versionso older clients reject skinny gems through both the local and remote install paths.">= 4.1.0.a"
Class Method Summary
-
.build(spec, skip_validation = false, strict_validation = false, file_name = nil, ruby_abi = nil)
Builds the gem described by #spec and returns the built file name; passing ruby_abi ("X.Y") builds a content-addressable gem named by the SHA-256 of its contents, updates
spec.required_ruby_versionto "~>X.Y.0", and constrainsspec.required_rubygems_versionto at least MINIMUM_RUBYGEMS_VERSION (incompatible withfile_name). -
.new(gem, security_policy = nil) ⇒ Package
constructor
Creates a new
Packagefor the file at #gem. -
.raw_spec(path, security_policy = nil)
Extracts the
Specificationand raw metadata from the #gem file at path.
Instance Attribute Summary
-
#checksums
readonly
Checksums for the contents of the package.
-
#data_mode
rw
Permission for other files.
-
#dir_mode
rw
Permission for directories.
-
#files
readonly
The files in this package.
-
#gem
readonly
Reference to the gem being packaged.
-
#prog_mode
rw
Permission for program files.
-
#security_policy
rw
The security policy used for verifying the contents of this package.
-
#spec
rw
The spec for this gem.
-
#spec=(value)
rw
Sets the
Specificationto use to build this package. - #build_time rw Internal use only
DefaultUserInteraction - Included
Instance Method Summary
-
#add_checksums(tar)
Adds a checksum for each entry in the gem to checksums.yaml.gz.
-
#build(skip_validation = false, strict_validation = false)
Builds this package based on the specification set by #spec=
-
#build_content_addressable_file(ruby_abi, skip_validation = false, strict_validation = false)
Builds this package scoped to ruby_abi ("X.Y"), then writes it to a content-addressable file name derived from the SHA-256 digest of the gem contents, e.g. "example-1.0-01234567.gem".
-
#content_address
Derives the content address from the gem's file name and verifies it against the SHA256 digest of the file contents.
-
#contents
A list of file names contained in this gem.
-
#copy_to(path)
Copies this package to path (if possible).
-
#extract_files(destination_dir, pattern = "*")
Extracts the files in this package into
destination_dir -
#gzip_to(io)
Gzips content written to
gz_iotoio. -
#initialize(gem, security_policy) ⇒ Package
constructor
Creates a new package that will read or write to the file #gem.
-
#read_checksums(gem)
Reads and loads checksums.yaml.gz from the tar file #gem
-
#setup_signer(signer_options: {})
Prepares the gem for signing and checksum generation.
-
#verify
Verifies that this gem:
-
#create_symlink(old_name, new_name)
private
Create a symlink and fallback to copy the file or directory on Windows, where symlink creation needs special privileges in form of the Developer Mode.
-
#excludes_rubygems_floor?(requirement, floor) ⇒ Boolean
private
Whether
requirementexcludes every RubyGems version satisfying thefloor, so that no RubyGems could install the built gem. - #limit_read(io, name, limit) private
-
#normalized_required_rubygems_version(ruby_abi)
private
The
required_rubygems_versionfor a content-addressable build: the spec's requirement raised to at least MINIMUM_RUBYGEMS_VERSION, warning if it had to be changed. -
#satisfies_rubygems_floor?(requirement, floor) ⇒ Boolean
private
Whether one of the lower bounds of
requirementalready guarantees thefloor. -
#validate_ruby_abi(ruby_abi)
private
Validates that the spec can be built as a content-addressable gem scoped to ruby_abi ("X.Y"): the ABI must be well-formed, the spec must declare a non-Ruby platform, and any existing
required_ruby_versionmust match the ABI. -
#verify_entry(entry)
private
Verifies
entryin a #gem file. -
#verify_files(gem)
private
Verifies the files of the #gem
-
#add_contents(tar)
Internal use only
Adds the files listed in the packages's
Specificationto data.tar.gz and adds this file to thetar. -
#add_files(tar)
Internal use only
Adds files included the package's
Specificationto thetarfile. -
#add_metadata(tar)
Internal use only
Adds the package's
Specificationto thetarfile. -
#digest(entry)
Internal use only
Creates a digest of the TarEntry
entryfrom the digest algorithm set by the security policy. -
#extract_tar_gz(io, destination_dir, pattern = "*")
Internal use only
Extracts all the files in the gzipped tar archive
iointodestination_dir. - #file_mode(mode) Internal use only
-
#load_spec_from_metadata(entry)
Internal use only
Loads a
Specificationfrom the TarEntryentry -
#normalize_path(pathname)
Internal use only
See additional method definition at line 589.
-
#open_tar_gz(io)
Internal use only
Opens
ioas a gzipped tar archive. -
#copy_stream(src, dst, size)
private
Internal use only
See additional method definition at line 903.
-
#install_location(filename, destination_dir)
private
Internal use only
Returns the full path for installing
filenameintodestination_dir, which must already be resolved withFile.realpathby the caller. -
#verify_checksums(digests, checksums)
private
Internal use only
Verifies the #checksums against the
digests. -
#verify_extraction_dir(dir, destination_dir)
private
Internal use only
Raises an exception unless dir, with symlinks resolved, is
destination_diror a directory inside it.
UserInteraction - Included
| #alert | Displays an alert |
| #alert_error | Displays an error |
| #alert_warning | Displays a warning |
| #ask | Asks a |
| #ask_for_password | Asks for a password with a |
| #ask_yes_no | Asks a yes or no |
| #choose_from_list | Asks the user to answer |
| #say | Displays the given |
| #terminate_interaction | Terminates the RubyGems process with the given |
| #verbose | Calls |
DefaultUserInteraction - Included
Text - Included
| #clean_text | Remove any non-printable characters and make the text suitable for printing. |
| #format_text | Wraps |
| #levenshtein_distance | Returns a value representing the "cost" of transforming str1 into str2 Vendored version of |
| #truncate_text, #min3 | |
Constructor Details
.new(gem, security_policy = nil) ⇒ Package
Creates a new 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 Package::Old will be
returned.
# File 'lib/rubygems/package.rb', line 181
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 self == Gem::Package return super unless gem.present? return super unless gem.start return super unless gem.start.include? "MD5SUM =" Gem::Package::Old.new gem, security_policy end
#initialize(gem, security_policy) ⇒ Package
Creates a new package that will read or write to the file #gem.
# File 'lib/rubygems/package.rb', line 228
def initialize(gem, security_policy) # :notnew: require "zlib" @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
Class Method Details
.build(spec, skip_validation = false, strict_validation = false, file_name = nil, ruby_abi = nil)
Builds the gem described by #spec and returns the built file name;
passing Gem.ruby_abi ("X.Y") builds a content-addressable gem named by the
SHA-256 of its contents, updates spec.required_ruby_version to
"~> X.Y.0", and constrains spec.required_rubygems_version to at least
MINIMUM_RUBYGEMS_VERSION (incompatible with
file_name).
# File 'lib/rubygems/package.rb', line 147
def self.build(spec, skip_validation = false, strict_validation = false, file_name = nil, ruby_abi = nil) if ruby_abi && file_name raise ArgumentError, "Cannot specify both a Ruby ABI and an output file name because content addressable gems must use the generated file name." end if ruby_abi require "digest" require "stringio" io = StringIO.new io.set_encoding(Encoding::BINARY) package = new io package.spec = spec.dup gem_file = package.build_content_addressable_file ruby_abi, skip_validation, strict_validation spec.required_ruby_version = package.spec.required_ruby_version spec.required_rubygems_version = package.spec.required_rubygems_version else gem_file = file_name || spec.file_name package = new gem_file package.spec = spec package.build skip_validation, strict_validation end gem_file end
.raw_spec(path, security_policy = nil)
Extracts the Specification and raw metadata from the #gem file at
Gem.path.
# File 'lib/rubygems/package.rb', line 204
def self.raw_spec(path, security_policy = nil) format = new(path, security_policy) spec = format.spec = 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 = entry.read when "metadata.gz" then = Gem::Util.gunzip entry.read end end end [spec, ] end
Instance Attribute Details
#build_time (rw)
# File 'lib/rubygems/package.rb', line 92
attr_accessor :build_time # :nodoc:
#checksums (readonly)
Checksums for the contents of the package
# File 'lib/rubygems/package.rb', line 97
attr_reader :checksums
#data_mode (rw)
Permission for other files
# File 'lib/rubygems/package.rb', line 130
attr_accessor :data_mode
#dir_mode (rw)
Permission for directories
# File 'lib/rubygems/package.rb', line 122
attr_accessor :dir_mode
#files (readonly)
The files in this package. This is not the contents of the gem, just the files in the top-level container.
# File 'lib/rubygems/package.rb', line 103
attr_reader :files
#gem (readonly)
Reference to the gem being packaged.
# File 'lib/rubygems/package.rb', line 108
attr_reader :gem
#prog_mode (rw)
Permission for program files
# File 'lib/rubygems/package.rb', line 126
attr_accessor :prog_mode
#security_policy (rw)
The security policy used for verifying the contents of this package.
# File 'lib/rubygems/package.rb', line 113
attr_accessor :security_policy
#spec (rw)
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 'lib/rubygems/package.rb', line 677
def spec verify unless @spec @spec end
#spec=(value) (rw)
Sets the Specification to use to build this package.
# File 'lib/rubygems/package.rb', line 118
attr_writer :spec
Instance Method Details
#add_checksums(tar)
Adds a checksum for each entry in the gem to checksums.yaml.gz.
# File 'lib/rubygems/package.rb', line 269
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", 0o444, @signer do |io| gzip_to io do |gz_io| if Gem.use_psych? Psych.dump checksums_by_algorithm, gz_io else gz_io.write Gem::YAMLSerializer.dump(checksums_by_algorithm) end end end end
#add_contents(tar)
Adds the files listed in the packages's Specification to data.tar.gz
and adds this file to the tar.
#add_files(tar)
Adds files included the package's Specification to the tar file
# File 'lib/rubygems/package.rb', line 310
def add_files(tar) # :nodoc: @spec.files.each do |file| stat = File.lstat file if stat.symlink? tar.add_symlink file, File.readlink(file), stat.mode end next unless stat.file? tar.add_file_simple file, stat.mode, stat.size do |dst_io| File.open file, "rb" do |src_io| copy_stream(src_io, dst_io, stat.size) end end end end
#add_metadata(tar)
Adds the package's Specification to the tar file
# File 'lib/rubygems/package.rb', line 331
def (tar) # :nodoc: digests = tar.add_file_signed "metadata.gz", 0o444, @signer do |io| gzip_to io do |gz_io| gz_io.write @spec.to_yaml end end @checksums["metadata.gz"] = digests end
#build(skip_validation = false, strict_validation = false)
Builds this package based on the specification set by #spec=
# File 'lib/rubygems/package.rb', line 344
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.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| gem add_contents gem add_checksums gem end end = <<-EOM Successfully built RubyGem Name: #{@spec.name} Version: #{@spec.version} EOM += " File: #{File.basename(@gem.path)}\n" if @gem.path say ensure @signer = nil end
#build_content_addressable_file(ruby_abi, skip_validation = false, strict_validation = false)
Builds this package scoped to Gem.ruby_abi ("X.Y"), then writes it to a content-addressable file name derived from the SHA-256 digest of the gem contents, e.g. "example-1.0-01234567.gem". Returns the file name of the written gem.
The spec is validated for an ABI-scoped build and its
required_ruby_version and required_rubygems_version are constrained
before building, so every gem this method produces is eligible for
content addressing.
# File 'lib/rubygems/package.rb', line 388
def build_content_addressable_file(ruby_abi, skip_validation = false, strict_validation = false) validate_ruby_abi ruby_abi @spec.required_rubygems_version = normalized_required_rubygems_version(ruby_abi) @spec.required_ruby_version = Gem::ContentAddress.ruby_abi_requirement(ruby_abi) build skip_validation, strict_validation bytes = @gem.with_read_io(&:read) gem_file = "#{@spec.name}-#{@spec.version}-#{Gem::ContentAddress.address_for(bytes)}.gem" File.binwrite(gem_file, bytes) say " File: #{gem_file}" say " Platform: #{@spec.platform}" say " Ruby ABI: #{ruby_abi}" gem_file end
#content_address
Derives the content address from the gem's file name and verifies it against the SHA256 digest of the file contents.
# File 'lib/rubygems/package.rb', line 255
def content_address path = @gem&.path return unless path address = Gem::ContentAddress.verified_file_name_claim(path, spec) return nil unless address return nil unless Gem::ContentAddress.eligible?(spec) address end
#contents
A list of file names contained in this gem
# File 'lib/rubygems/package.rb', line 409
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 rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e raise Gem::Package::FormatError.new e., @gem end
#copy_stream(src, dst, size) (private)
See additional method definition at line 903.
# File 'lib/rubygems/package.rb', line 907
def copy_stream(src, dst, size) # :nodoc: dst.write src.read(size) end
#copy_to(path)
Copies this package to Gem.path (if possible)
#create_symlink(old_name, new_name) (private)
Create a symlink and fallback to copy the file or directory on Windows, where symlink creation needs special privileges in form of the Developer Mode. JRuby on Windows raises TypeError from the wincode path-conversion helper when it cannot create the symlink, so fall back to copy in that case too.
See additional method definition at line 923.
# File 'lib/rubygems/package.rb', line 930
def create_symlink(old_name, new_name) File.symlink(old_name, new_name) rescue Errno::EACCES, TypeError from = File.(old_name, File.dirname(new_name)) FileUtils.cp_r(from, new_name) end
#digest(entry)
Creates a digest of the TarEntry entry from the digest algorithm set by
the security policy.
# File 'lib/rubygems/package.rb', line 439
def digest(entry) # :nodoc: algorithms = if @checksums @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 return @digests if algorithms.nil? || algorithms.empty? 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 @digests end
#excludes_rubygems_floor?(requirement, floor) ⇒ Boolean (private)
Whether requirement excludes every RubyGems version satisfying the
floor, so that no RubyGems could install the built gem.
# File 'lib/rubygems/package.rb', line 762
def excludes_rubygems_floor?(requirement, floor) capped_below_floor = requirement.requirements.any? do |op, version| case op when "<" then version <= floor when "<=", "=" then version < floor when "~>" then version.bump <= floor.release else false end end return true if capped_below_floor !requirement.satisfied_by?(floor) && requirement.requirements.any? do |op, version| ["<=", "="].include?(op) && version == floor end end
#extract_files(destination_dir, pattern = "*")
Extracts the files in this package into destination_dir
If pattern is specified, only entries matching that glob will be
extracted.
# File 'lib/rubygems/package.rb', line 468
def extract_files(destination_dir, pattern = "*") verify unless @spec FileUtils.mkdir_p destination_dir, mode: dir_mode && 0o755 @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 break # ignore further entries end end rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e raise Gem::Package::FormatError.new e., @gem end
#extract_tar_gz(io, destination_dir, pattern = "*")
Extracts all the files in the gzipped tar archive io into
destination_dir.
If an entry in the archive contains a relative path above
destination_dir or an absolute path is encountered an exception is
raised.
If pattern is specified, only entries matching that glob will be
extracted.
# File 'lib/rubygems/package.rb', line 499
def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc: destination_dir = File.realpath(destination_dir) directories = [] symlinks = [] open_tar_gz io do |tar| tar.each do |entry| full_name = entry.full_name next unless File.fnmatch pattern, full_name, File::FNM_DOTMATCH destination = install_location full_name, destination_dir if entry.symlink? link_target = entry.header.linkname real_destination = File.(link_target, File.dirname(destination)) raise Gem::Package::SymlinkError.new(full_name, real_destination, destination_dir) unless normalize_path(real_destination).start_with? normalize_path(destination_dir + "/") symlinks << [full_name, link_target, destination, real_destination] end mkdir = if entry.directory? destination else File.dirname destination end unless directories.include?(mkdir) FileUtils.mkdir_p mkdir, mode: dir_mode ? 0o755 : (entry.header.mode if entry.directory?) directories << mkdir end verify_extraction_dir mkdir, destination_dir if entry.file? File.open(destination, "wb") do |out| copy_stream(tar.io, out, entry.size) # Flush needs to happen before chmod because there could be data # in the IO buffer that needs to be written, and that could be # written after the chmod (on close) which would mess up the perms out.flush out.chmod file_mode(entry.header.mode) & ~File.umask end end verbose destination end end symlinks.each do |name, target, destination, real_destination| if File.exist?(real_destination) create_symlink(target, destination) else alert_warning "#{@spec.full_name} ships with a dangling symlink named #{name} pointing to missing #{target} file. Ignoring" end end if dir_mode && !directories.empty? File.chmod(dir_mode, *directories) end end
#file_mode(mode)
# File 'lib/rubygems/package.rb', line 564
def file_mode(mode) # :nodoc: ((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 # gets put into an unsigned short). (mode & ((1 << 16) - 1)) end
#gzip_to(io)
Gzips content written to gz_io to io.
# File 'lib/rubygems/package.rb', line 579
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
#install_location(filename, destination_dir) (private)
Returns the full path for installing filename into destination_dir,
which must already be resolved with File.realpath by the caller.
If filename is not inside destination_dir an exception is raised.
# File 'lib/rubygems/package.rb', line 814
def install_location(filename, destination_dir) # :nodoc: raise Gem::Package::PathError.new(filename, destination_dir) if filename.start_with? "/" destination = File.(filename, destination_dir) raise Gem::Package::PathError.new(destination, destination_dir) unless normalize_path(destination).start_with? normalize_path(destination_dir + "/") destination end
#limit_read(io, name, limit) (private)
# File 'lib/rubygems/package.rb', line 912
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
#load_spec_from_metadata(entry)
Loads a Specification from the TarEntry entry
# File 'lib/rubygems/package.rb', line 601
def (entry) # :nodoc: limit = 10 * 1024 * 1024 case entry.full_name when "metadata" then @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 limit_read(gzio, "metadata.gz", limit) end end end
#normalize_path(pathname)
See additional method definition at line 589.
# File 'lib/rubygems/package.rb', line 593
def normalize_path(pathname) # :nodoc: pathname.downcase end
#normalized_required_rubygems_version(ruby_abi) (private)
The required_rubygems_version for a content-addressable build: the
spec's requirement raised to at least MINIMUM_RUBYGEMS_VERSION, warning
if it had to be changed. Raises if the requirement excludes every version
satisfying that floor, since no RubyGems could install the built gem.
# File 'lib/rubygems/package.rb', line 728
def normalized_required_rubygems_version(ruby_abi) minimum = Gem::Requirement.new(MINIMUM_RUBYGEMS_VERSION) existing = @spec.required_rubygems_version return minimum if existing.nil? || existing == Gem::Requirement.default floor = minimum.requirements.first.last if excludes_rubygems_floor?(existing, floor) raise ArgumentError, "Cannot build gem for Ruby ABI #{ruby_abi} because required_rubygems_version is set to #{existing}, " \ "which excludes RubyGems #{MINIMUM_RUBYGEMS_VERSION} required to install content addressable gems. " \ "Please remove or loosen the conflicting constraint." end return existing if satisfies_rubygems_floor?(existing, floor) preserved = existing.requirements.filter_map do |op, version| "#{op} #{version}" if ["~>", "<", "<=", "!="].include?(op) end normalized = Gem::Requirement.new([MINIMUM_RUBYGEMS_VERSION, *preserved]) alert_warning \ "required_rubygems_version was changed from \"#{existing}\" to \"#{normalized}\" for this build " \ "because content addressable gems can only be installed by RubyGems #{MINIMUM_RUBYGEMS_VERSION}." normalized end
#open_tar_gz(io)
Opens io as a gzipped tar archive
# File 'lib/rubygems/package.rb', line 616
def open_tar_gz(io) # :nodoc: Zlib::GzipReader.wrap io do |gzio| tar = Gem::Package::TarReader.new gzio yield tar ensure # Consume remaining gzip data to prevent the # "attempt to close unfinished zstream; reset forced" warning # when the GzipReader is closed with unconsumed compressed data. begin IO.copy_stream(gzio, IO::NULL) rescue Zlib::GzipFile::Error, IOError nil end end end
#read_checksums(gem)
Reads and loads checksums.yaml.gz from the tar file #gem
#satisfies_rubygems_floor?(requirement, floor) ⇒ Boolean (private)
Whether one of the lower bounds of requirement already guarantees the
floor.
# File 'lib/rubygems/package.rb', line 783
def satisfies_rubygems_floor?(requirement, floor) requirement.requirements.any? do |op, version| case op when ">=", "~>", "=", ">" then version >= floor else false end end end
#setup_signer(signer_options: {})
Prepares the gem for signing and checksum generation. If a signing certificate and key are not present only checksum generation is set up.
# File 'lib/rubygems/package.rb', line 650
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, ) @spec.signing_key = nil @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(&:to_pem) if @signer.cert_chain end end
#validate_ruby_abi(ruby_abi) (private)
Validates that the spec can be built as a content-addressable gem scoped
to Gem.ruby_abi ("X.Y"): the ABI must be well-formed, the spec must declare
a non-Ruby platform, and any existing required_ruby_version must match
the ABI.
# File 'lib/rubygems/package.rb', line 798
def validate_ruby_abi(ruby_abi) if !Gem::ContentAddress.valid_ruby_abi?(ruby_abi) raise ArgumentError, "Ruby ABI must be in X.Y format" elsif !Gem::ContentAddress.platform_eligible?(@spec.platform) raise ArgumentError, "Cannot build a gem scoped to a single Ruby ABI as no platform or a Ruby platform has been set" elsif !Gem::ContentAddress.ruby_abi_compatible?(@spec, ruby_abi) raise ArgumentError, "Cannot build gem for Ruby ABI #{ruby_abi} because required_ruby_version is set to #{@spec.required_ruby_version}. Please set required_ruby_version to \"~> #{ruby_abi}.0\"." end end
#verify
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 'lib/rubygems/package.rb', line 693
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 true rescue Gem::Security::Exception @spec = nil @files = [] raise rescue Errno::ENOENT => e raise Gem::Package::FormatError.new e. rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e raise Gem::Package::FormatError.new e., @gem end
#verify_checksums(digests, checksums) (private)
Verifies the #checksums against the digests. This check is not
cryptographically secure. Missing checksums are ignored.
# File 'lib/rubygems/package.rb', line 844
def verify_checksums(digests, checksums) # :nodoc: return unless checksums checksums.sort.each do |algorithm, gem_digests| gem_digests.sort.each do |file_name, gem_hexdigest| computed_digest = digests[algorithm][file_name] unless computed_digest.hexdigest == gem_hexdigest raise Gem::Package::FormatError.new \ "#{algorithm} checksum mismatch for #{file_name}", @gem end end end end
#verify_entry(entry) (private)
Verifies entry in a #gem file.
# File 'lib/rubygems/package.rb', line 862
def verify_entry(entry) file_name = entry.full_name @files << file_name case file_name when /\.sig$/ then @signatures[$`] = limit_read(entry, file_name, 1024 * 1024) if @security_policy return else digest entry end entry rescue StandardError warn "Exception while verifying #{@gem.path}" raise end
#verify_extraction_dir(dir, destination_dir) (private)
Raises an exception unless Gem.dir, with symlinks resolved, is
destination_dir or a directory inside it. destination_dir must
already be resolved with File.realpath by the caller.
# File 'lib/rubygems/package.rb', line 831
def verify_extraction_dir(dir, destination_dir) # :nodoc: real_dir = File.realpath(dir) return if real_dir == destination_dir || normalize_path(real_dir).start_with?(normalize_path(destination_dir + "/")) raise Gem::Package::PathError.new(real_dir, destination_dir) end
#verify_files(gem) (private)
Verifies the files of the #gem
# File 'lib/rubygems/package.rb', line 883
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)) && duplicates.any? raise Gem::Security::Exception, "duplicate files in the package: (#{duplicates.map(&:inspect).join(", ")})" end end