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 149
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, content_addressable: false)
Builds the gem described by #spec and returns the built file 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(skip_validation = false, strict_validation = false)
Builds this package and writes it to a content-addressable file name derived from the SHA-256 digest of the gem contents, for example "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. -
#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
-
#invalid_windows_filename?(filename) ⇒ Boolean
Internal use only
Checks if a filename contains characters that are invalid on Windows.
-
#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 609.
-
#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 917.
-
#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 194
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 241
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, content_addressable: false)
Builds the gem described by #spec and returns the built file name.
When content_addressable is true, builds a gem named by the SHA-256
digest of its contents. The spec must declare a non-Ruby platform and a
required_ruby_version identifying a single Ruby ABI. Its
required_rubygems_version is constrained to at least
MINIMUM_RUBYGEMS_VERSION. Content-addressable builds are incompatible
with file_name.
# File 'lib/rubygems/package.rb', line 161
def self.build(spec, skip_validation = false, strict_validation = false, file_name = nil, content_addressable: false) if content_addressable && file_name raise ArgumentError, "Cannot specify an output file name for a content-addressable gem as these gems must use the generated file name." end if content_addressable 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 skip_validation, strict_validation 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 217
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 104
attr_accessor :build_time # :nodoc:
#checksums (readonly)
Checksums for the contents of the package
# File 'lib/rubygems/package.rb', line 109
attr_reader :checksums
#data_mode (rw)
Permission for other files
# File 'lib/rubygems/package.rb', line 142
attr_accessor :data_mode
#dir_mode (rw)
Permission for directories
# File 'lib/rubygems/package.rb', line 134
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 115
attr_reader :files
#gem (readonly)
Reference to the gem being packaged.
# File 'lib/rubygems/package.rb', line 120
attr_reader :gem
#prog_mode (rw)
Permission for program files
# File 'lib/rubygems/package.rb', line 138
attr_accessor :prog_mode
#security_policy (rw)
The security policy used for verifying the contents of this package.
# File 'lib/rubygems/package.rb', line 125
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 707
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 130
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 282
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 323
def add_files(tar) # :nodoc: @spec.files.each do |file| if invalid_windows_filename?(file) alert_warning "filename '#{file}' contains characters that are invalid on Windows (e.g., colons). This gem may fail to install on Windows." end 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 348
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 361
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(skip_validation = false, strict_validation = false)
Builds this package and writes it to a content-addressable file name derived from the SHA-256 digest of the gem contents, for example "example-1.0-01234567.gem". Returns the written file name.
The spec is validated for content addressing. Its
required_ruby_version determines the Ruby ABI, while its
required_rubygems_version is constrained before building.
# File 'lib/rubygems/package.rb', line 403
def build_content_addressable_file(skip_validation = false, strict_validation = false) Gem::ContentAddress.eligible!(@spec) ruby_abi = Gem::ContentAddress.ruby_abi_for(@spec.required_ruby_version) @spec.required_rubygems_version = normalized_required_rubygems_version(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 268
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 424
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 917.
# File 'lib/rubygems/package.rb', line 921
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 937.
# File 'lib/rubygems/package.rb', line 944
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 454
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 792
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 483
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 514
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 if Gem.win_platform? && invalid_windows_filename?(full_name) gem_name = @spec ? @spec.full_name : "unknown" raise Gem::Package::InvalidWindowsFileNameError.new(full_name, gem_name) end 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 & 0o777 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 584
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 permission bits from the tarball. Masking drops setuid, # setgid and sticky bits along with anything that does not fit into a # mode in CRuby (an unsigned short). (mode & 0o777) end
#gzip_to(io)
Gzips content written to gz_io to io.
# File 'lib/rubygems/package.rb', line 599
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 828
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
#invalid_windows_filename?(filename) ⇒ Boolean
Checks if a filename contains characters that are invalid on Windows. Windows doesn't allow: < > : " | ? * \ and control characters (0x00-0x1F). Colons are the most common issue since they're allowed on Unix. Note: Colons are only valid as drive letter separators (e.g., C:), not in filenames.
# File 'lib/rubygems/package.rb', line 624
def invalid_windows_filename?(filename) # :nodoc: filename.to_s.split("/").any? {|part| part.match?(/[:<>"|?*\\\x00-\x1f]/) } end
#limit_read(io, name, limit) (private)
# File 'lib/rubygems/package.rb', line 926
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 631
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 609.
# File 'lib/rubygems/package.rb', line 613
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 758
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 646
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 813
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 680
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
#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 723
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 858
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 876
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 845
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 897
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