summaryrefslogtreecommitdiff
path: root/lib/bundler
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bundler')
-rw-r--r--lib/bundler/checksum.rb218
-rw-r--r--lib/bundler/definition.rb8
-rw-r--r--lib/bundler/endpoint_specification.rb12
-rw-r--r--lib/bundler/fetcher.rb13
-rw-r--r--lib/bundler/lazy_specification.rb15
-rw-r--r--lib/bundler/lockfile_generator.rb13
-rw-r--r--lib/bundler/lockfile_parser.rb80
-rw-r--r--lib/bundler/plugin/api/source.rb3
-rw-r--r--lib/bundler/remote_specification.rb44
-rw-r--r--lib/bundler/rubygems_gem_installer.rb61
-rw-r--r--lib/bundler/source.rb2
-rw-r--r--lib/bundler/source/path.rb1
-rw-r--r--lib/bundler/source/rubygems.rb3
-rw-r--r--lib/bundler/stub_specification.rb10
14 files changed, 341 insertions, 142 deletions
diff --git a/lib/bundler/checksum.rb b/lib/bundler/checksum.rb
index 0b618d5033..3b03935b57 100644
--- a/lib/bundler/checksum.rb
+++ b/lib/bundler/checksum.rb
@@ -2,31 +2,194 @@
module Bundler
class Checksum
+ class Store
+ attr_reader :store
+ protected :store
+
+ def initialize
+ @store = {}
+ end
+
+ def initialize_copy(o)
+ @store = {}
+ o.store.each do |k, v|
+ @store[k] = v.dup
+ end
+ end
+
+ def [](spec)
+ sums = @store[spec.full_name]
+
+ Checksum.new(spec.name, spec.version, spec.platform, sums&.transform_values(&:digest))
+ end
+
+ def register(spec, checksums)
+ register_full_name(spec.full_name, checksums)
+ end
+
+ def register_triple(name, version, platform, checksums)
+ register_full_name(GemHelpers.spec_full_name(name, version, platform), checksums)
+ end
+
+ def delete_full_name(full_name)
+ @store.delete(full_name)
+ end
+
+ def register_full_name(full_name, checksums)
+ sums = (@store[full_name] ||= {})
+
+ checksums.each do |checksum|
+ algo = checksum.algo
+ if multi = sums[algo]
+ multi.merge(checksum)
+ else
+ sums[algo] = Multi.new [checksum]
+ end
+ end
+ rescue SecurityError => e
+ raise e.exception(<<~MESSAGE)
+ Bundler found multiple different checksums for #{full_name}.
+ This means that there are multiple different `#{full_name}.gem` files.
+ This is a potential security issue, since Bundler could be attempting \
+ to install a different gem than what you expect.
+
+ #{e.message}
+ To resolve this issue:
+ 1. delete any downloaded gems referenced above
+ 2. run `bundle install`
+
+ If you are sure that the new checksum is correct, you can \
+ remove the `#{full_name}` entry under the lockfile `CHECKSUMS` \
+ section and rerun `bundle install`.
+
+ If you wish to continue installing the downloaded gem, and are certain it does not pose a \
+ security issue despite the mismatching checksum, do the following:
+ 1. run `bundle config set --local disable_checksum_validation true` to turn off checksum verification
+ 2. run `bundle install`
+ MESSAGE
+ end
+
+ def use(other)
+ other.store.each do |k, v|
+ register_full_name k, v.values
+ end
+ end
+ end
+
+ class Single
+ attr_reader :algo, :digest, :source
+ def initialize(algo, digest, source)
+ @algo = algo
+ @digest = digest
+ @source = source
+ end
+
+ def ==(other)
+ other.is_a?(Single) && other.digest == digest && other.algo == algo && source == other.source
+ end
+
+ def hash
+ digest.hash
+ end
+
+ alias_method :eql?, :==
+
+ def to_s
+ "#{algo}-#{digest} (from #{source})"
+ end
+ end
+
+ class Multi
+ attr_reader :algo, :digest, :checksums
+ protected :checksums
+
+ def initialize(checksums)
+ @checksums = checksums
+
+ unless checksums && checksums.size > 0
+ raise ArgumentError, "must provide at least one checksum"
+ end
+
+ first = checksums.first
+ @algo = first.algo
+ @digest = first.digest
+ end
+
+ def initialize_copy(o)
+ @checksums = o.checksums.dup
+ @algo = o.algo
+ @digest = o.digest
+ end
+
+ def merge(other)
+ raise ArgumentError, "cannot merge checksums of different algorithms" unless algo == other.algo
+ unless digest == other.digest
+ raise SecurityError, <<~MESSAGE
+ #{other}
+ #{self} from:
+ * #{sources.join("\n* ")}
+ MESSAGE
+ end
+
+ case other
+ when Single
+ @checksums << other
+ when Multi
+ @checksums.concat(other.checksums)
+ else
+ raise ArgumentError
+ end
+ @checksums.uniq!
+
+ self
+ end
+
+ def sources
+ @checksums.map(&:source)
+ end
+
+ def to_s
+ "#{algo}-#{digest}"
+ end
+ end
+
attr_reader :name, :version, :platform, :checksums
SHA256 = %r{\Asha256-([a-z0-9]{64}|[A-Za-z0-9+\/=]{44})\z}.freeze
+ private_constant :SHA256
- def initialize(name, version, platform, checksums = [])
+ def initialize(name, version, platform, checksums = {})
@name = name
@version = version
@platform = platform || Gem::Platform::RUBY
- @checksums = checksums
+ @checksums = checksums || {}
# can expand this validation when we support more hashing algos later
- if @checksums.any? && @checksums.all? {|c| c !~ SHA256 }
+ if [email protected]_a?(::Hash) || (@checksums.any? && [email protected]?("sha256"))
+ raise ArgumentError, "invalid checksums (#{@checksums.inspect})"
+ end
+ if @checksums.any? {|_, checksum| !checksum.is_a?(String) }
raise ArgumentError, "invalid checksums (#{@checksums})"
end
end
- def self.digest_from_file_source(file_source)
+ def self.digests_from_file_source(file_source, digest_algorithms: %w[sha256])
raise ArgumentError, "not a valid file source: #{file_source}" unless file_source.respond_to?(:with_read_io)
+ digests = digest_algorithms.map do |digest_algorithm|
+ [digest_algorithm.to_s, Bundler::SharedHelpers.digest(digest_algorithm.upcase).new]
+ end.to_h
+
file_source.with_read_io do |io|
- digest = Bundler::SharedHelpers.digest(:SHA256).new
- digest << io.read(16_384) until io.eof?
+ until io.eof?
+ block = io.read(16_384)
+ digests.each_value {|digest| digest << block }
+ end
+
io.rewind
- digest
end
+
+ digests
end
def full_name
@@ -42,12 +205,51 @@ module Bundler
def to_lock
out = String.new
out << " #{GemHelpers.lock_name(name, version, platform)}"
- out << " #{sha256}" if sha256
+ checksums.sort_by(&:first).each_with_index do |(algo, checksum), idx|
+ out << (idx.zero? ? " " : ",")
+ out << algo << "-" << checksum
+ end
out << "\n"
out
end
+ def match?(other)
+ return false unless match_spec?(other)
+ match_digests?(other.checksums)
+ end
+
+ def match_digests?(digests)
+ return true if checksums.empty? && digests.empty?
+
+ common_algos = checksums.keys & digests.keys
+ return true if common_algos.empty?
+
+ common_algos.all? do |algo|
+ checksums[algo] == digests[algo]
+ end
+ end
+
+ def merge!(other)
+ raise ArgumentError, "can't merge checksums for different specs" unless match_spec?(other)
+
+ merge_digests!(other.checksums)
+ end
+
+ def merge_digests!(digests)
+ if digests.any? {|_, checksum| !checksum.is_a?(String) }
+ raise ArgumentError, "invalid checksums (#{digests})"
+ end
+ @checksums = @checksums.merge(digests) do |algo, ours, theirs|
+ if ours != theirs
+ raise ArgumentError, "Digest mismatch for #{algo}:\n\t* #{ours.inspect}\n\t* #{theirs.inspect}"
+ end
+ ours
+ end
+
+ self
+ end
+
private
def sha256
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index 14f6746331..26e3126d84 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -15,7 +15,6 @@ module Bundler
:dependencies,
:locked_deps,
:locked_gems,
- :locked_checksums,
:platforms,
:ruby_version,
:lockfile,
@@ -93,7 +92,6 @@ module Bundler
@locked_bundler_version = @locked_gems.bundler_version
@locked_ruby_version = @locked_gems.ruby_version
@originally_locked_specs = SpecSet.new(@locked_gems.specs)
- @locked_checksums = @locked_gems.checksums
if unlock != true
@locked_deps = @locked_gems.dependencies
@@ -114,7 +112,6 @@ module Bundler
@originally_locked_specs = @locked_specs
@locked_sources = []
@locked_platforms = []
- @locked_checksums = {}
end
locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
@@ -753,6 +750,11 @@ module Bundler
changes = sources.replace_sources!(@locked_sources)
sources.all_sources.each do |source|
+ # has to be done separately, because we want to keep the locked checksum
+ # store for a source, even when doing a full update
+ if @locked_gems && locked_source = @locked_gems.sources.find {|s| s == source }
+ source.checksum_store&.use(locked_source.checksum_store)
+ end
# If the source is unlockable and the current command allows an unlock of
# the source (for example, you are doing a `bundle update <foo>` of a git-pinned
# gem), unlock it. For git sources, this means to unlock the revision, which
diff --git a/lib/bundler/endpoint_specification.rb b/lib/bundler/endpoint_specification.rb
index 863544b1f9..943e33be94 100644
--- a/lib/bundler/endpoint_specification.rb
+++ b/lib/bundler/endpoint_specification.rb
@@ -125,7 +125,17 @@ module Bundler
next unless v
case k.to_s
when "checksum"
- @checksum = v.last
+ next if Bundler.settings[:disable_checksum_validation]
+ digest = v.last
+ if digest.length == 64
+ # nothing to do, it's a hexdigest
+ elsif digest.length == 44
+ # transform the bytes from base64 to hex
+ digest = digest.unpack("m0").first.unpack("H*").first
+ else
+ raise ArgumentError, "The given checksum for #{full_name} (#{digest.inspect}) is not a valid SHA256 hexdigest nor base64digest"
+ end
+ @checksum = Checksum::Single.new("sha256", digest, "API response from #{@spec_fetcher.uri}")
when "rubygems"
@required_rubygems_version = Gem::Requirement.new(v)
when "ruby"
diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb
index 5c1eac415e..08d1ee3437 100644
--- a/lib/bundler/fetcher.rb
+++ b/lib/bundler/fetcher.rb
@@ -81,7 +81,7 @@ module Bundler
:HTTPRequestURITooLong, :HTTPUnauthorized, :HTTPUnprocessableEntity,
:HTTPUnsupportedMediaType, :HTTPVersionNotSupported].freeze
FAIL_ERRORS = begin
- fail_errors = [AuthenticationRequiredError, BadAuthenticationError, AuthenticationForbiddenError, FallbackError]
+ fail_errors = [AuthenticationRequiredError, BadAuthenticationError, AuthenticationForbiddenError, FallbackError, SecurityError]
fail_errors << Gem::Requirement::BadRequirementError
fail_errors.concat(NET_ERRORS.map {|e| Net.const_get(e) })
end.freeze
@@ -139,7 +139,16 @@ module Bundler
fetch_specs(gem_names).each do |name, version, platform, dependencies, metadata|
spec = if dependencies
- EndpointSpecification.new(name, version, platform, self, dependencies, metadata)
+ EndpointSpecification.new(name, version, platform, self, dependencies, metadata).tap do |es|
+ unless index.local_search(es).empty?
+ # Duplicate spec.full_names, different spec.original_names
+ # index#<< ensures that the last one added wins, so if we're overriding
+ # here, make sure to also override the checksum, otherwise downloading the
+ # specs (even if that version is completely unused) will cause a SecurityError
+ source.checksum_store.delete_full_name(es.full_name)
+ end
+ source.checksum_store.register(es, [es.checksum]) if source && es.checksum
+ end
else
RemoteSpecification.new(name, version, platform, self)
end
diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb
index a17c8b90e5..2d084e462f 100644
--- a/lib/bundler/lazy_specification.rb
+++ b/lib/bundler/lazy_specification.rb
@@ -67,19 +67,6 @@ module Bundler
out
end
- def materialize_for_checksum(&blk)
- #
- # See comment about #ruby_platform_materializes_to_ruby_platform?
- # If the old lockfile format is present where there is no specific
- # platform, then we should skip locking checksums as it is not
- # deterministic which platform variant is locked.
- #
- return unless ruby_platform_materializes_to_ruby_platform?
-
- s = materialize_for_installation
- yield s if block_given?
- end
-
def materialize_for_installation
source.local!
@@ -126,7 +113,7 @@ module Bundler
end
def to_s
- @__to_s ||= GemHelpers.lock_name(name, version, platform)
+ @to_s ||= GemHelpers.lock_name(name, version, platform)
end
def git_version
diff --git a/lib/bundler/lockfile_generator.rb b/lib/bundler/lockfile_generator.rb
index 52b3b411aa..4d2c2c2a86 100644
--- a/lib/bundler/lockfile_generator.rb
+++ b/lib/bundler/lockfile_generator.rb
@@ -68,16 +68,11 @@ module Bundler
def add_checksums
out << "\nCHECKSUMS\n"
- definition.resolve.sort_by(&:full_name).each do |spec|
- checksum = spec.to_checksum if spec.respond_to?(:to_checksum)
- if spec.is_a?(LazySpecification)
- spec.materialize_for_checksum do |materialized_spec|
- checksum ||= materialized_spec.to_checksum if materialized_spec&.respond_to?(:to_checksum)
- end
- end
- checksum ||= definition.locked_checksums[spec.full_name]
- out << checksum.to_lock if checksum
+ empty_store = Checksum::Store.new
+
+ definition.resolve.sort_by(&:full_name).each do |spec|
+ out << (spec.source.checksum_store || empty_store)[spec].to_lock
end
end
diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb
index 001de06d53..43d544fd32 100644
--- a/lib/bundler/lockfile_parser.rb
+++ b/lib/bundler/lockfile_parser.rb
@@ -2,6 +2,28 @@
module Bundler
class LockfileParser
+ class Position
+ attr_reader :line, :column
+ def initialize(line, column)
+ @line = line
+ @column = column
+ end
+
+ def advance!(string)
+ lines = string.count("\n")
+ if lines > 0
+ @line += lines
+ @column = string.length - string.rindex("\n")
+ else
+ @column += string.length
+ end
+ end
+
+ def to_s
+ "#{line}:#{column}"
+ end
+ end
+
attr_reader :sources, :dependencies, :specs, :platforms, :bundler_version, :ruby_version, :checksums
BUNDLED = "BUNDLED WITH"
@@ -22,7 +44,7 @@ module Bundler
Gem::Version.create("1.10") => [BUNDLED].freeze,
Gem::Version.create("1.12") => [RUBY].freeze,
Gem::Version.create("1.13") => [PLUGIN].freeze,
- Gem::Version.create("2.4.0") => [CHECKSUMS].freeze,
+ Gem::Version.create("2.5.0") => [CHECKSUMS].freeze,
}.freeze
KNOWN_SECTIONS = SECTIONS_BY_VERSION_INTRODUCED.values.flatten!.freeze
@@ -66,15 +88,20 @@ module Bundler
@sources = []
@dependencies = {}
@parse_method = nil
- @checksums = {}
@specs = {}
+ @lockfile_path = begin
+ SharedHelpers.relative_lockfile_path
+ rescue GemfileNotFound
+ "Gemfile.lock"
+ end
+ @pos = Position.new(1, 1)
if lockfile.match?(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/)
- raise LockfileError, "Your lockfile contains merge conflicts.\n" \
- "Run `git checkout HEAD -- #{SharedHelpers.relative_lockfile_path}` first to get a clean lock."
+ raise LockfileError, "Your #{@lockfile_path} contains merge conflicts.\n" \
+ "Run `git checkout HEAD -- #{@lockfile_path}` first to get a clean lock."
end
- lockfile.split(/(?:\r?\n)+/) do |line|
+ lockfile.split(/((?:\r?\n)+)/).each_slice(2) do |line, whitespace|
if SOURCE.include?(line)
@parse_method = :parse_source
parse_source(line)
@@ -93,12 +120,15 @@ module Bundler
elsif @parse_method
send(@parse_method, line)
end
+ @pos.advance!(line)
+ @pos.advance!(whitespace)
end
@specs = @specs.values.sort_by!(&:full_name)
rescue ArgumentError => e
Bundler.ui.debug(e)
- raise LockfileError, "Your lockfile is unreadable. Run `rm #{SharedHelpers.relative_lockfile_path}` " \
- "and then `bundle install` to generate a new lockfile."
+ raise LockfileError, "Your lockfile is unreadable. Run `rm #{@lockfile_path}` " \
+ "and then `bundle install` to generate a new lockfile. The error occurred while " \
+ "evaluating #{@lockfile_path}:#{@pos}"
end
def may_include_redundant_platform_specific_gems?
@@ -149,7 +179,7 @@ module Bundler
(?:#{space}\(([^-]*) # Space, followed by version
(?:-(.*))?\))? # Optional platform
(!)? # Optional pinned marker
- (?:#{space}(.*))? # Optional checksum
+ (?:#{space}([^ ]+))? # Optional checksum
$ # Line end
/xo.freeze
@@ -183,19 +213,31 @@ module Bundler
end
def parse_checksum(line)
- if line =~ NAME_VERSION
- spaces = $1
- return unless spaces.size == 2
- name = $2
- version = $3
- platform = $4
- checksum = $6
+ return unless line =~ NAME_VERSION
- version = Gem::Version.new(version)
- platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY
- checksum = Bundler::Checksum.new(name, version, platform, [checksum])
- @checksums[checksum.full_name] = checksum
+ spaces = $1
+ return unless spaces.size == 2
+ name = $2
+ version = $3
+ platform = $4
+ checksums = $6
+ return unless checksums
+
+ version = Gem::Version.new(version)
+ platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY
+ source = "#{@lockfile_path}:#{@pos} in the CHECKSUMS lockfile section"
+ checksums = checksums.split(",").map do |c|
+ algo, digest = c.split("-", 2)
+ Checksum::Single.new(algo, digest, source)
end
+
+ full_name = GemHelpers.spec_full_name(name, version, platform)
+
+ # Don't raise exception if there's a checksum for a gem that's not in the lockfile,
+ # we prefer to heal invalid lockfiles
+ return unless spec = @specs[full_name]
+
+ spec.source.checksum_store.register_full_name(full_name, checksums)
end
def parse_spec(line)
diff --git a/lib/bundler/plugin/api/source.rb b/lib/bundler/plugin/api/source.rb
index 67c45bd204..57d4eb64ad 100644
--- a/lib/bundler/plugin/api/source.rb
+++ b/lib/bundler/plugin/api/source.rb
@@ -39,7 +39,7 @@ module Bundler
# is present to be compatible with `Definition` and is used by
# rubygems source.
module Source
- attr_reader :uri, :options, :name
+ attr_reader :uri, :options, :name, :checksum_store
attr_accessor :dependency_names
def initialize(opts)
@@ -48,6 +48,7 @@ module Bundler
@uri = opts["uri"]
@type = opts["type"]
@name = opts["name"] || "#{@type} at #{@uri}"
+ @checksum_store = Checksum::Store.new
end
# This is used by the default `spec` method to constructs the
diff --git a/lib/bundler/remote_specification.rb b/lib/bundler/remote_specification.rb
index e8054dbbd5..f626a3218e 100644
--- a/lib/bundler/remote_specification.rb
+++ b/lib/bundler/remote_specification.rb
@@ -93,56 +93,12 @@ module Bundler
" #{source.revision[0..6]}"
end
- # we don't get the checksum from a server like we could with EndpointSpecs
- # calculating the checksum from the file on disk still provides some measure of security
- # if it changes from install to install, that is cause for concern
- def to_checksum
- @checksum ||= begin
- gem_path = fetch_gem
- require "rubygems/package"
- package = Gem::Package.new(gem_path)
- digest = Bundler::Checksum.digest_from_file_source(package.gem)
- digest.hexdigest!
- end
-
- digest = "sha256-#{@checksum}" if @checksum
- Bundler::Checksum.new(name, version, platform, [digest])
- end
-
private
def to_ary
nil
end
- def fetch_gem
- fetch_platform
-
- cache_path = download_cache_path || default_cache_path_for_rubygems_dir
- gem_path = "#{cache_path}/#{file_name}"
- return gem_path if File.exist?(gem_path)
-
- SharedHelpers.filesystem_access(cache_path) do |p|
- FileUtils.mkdir_p(p)
- end
-
- Bundler.rubygems.download_gem(self, remote.uri, cache_path)
-
- gem_path
- end
-
- def download_cache_path
- return unless Bundler.feature_flag.global_gem_cache?
- return unless remote
- return unless remote.cache_slug
-
- Bundler.user_cache.join("gems", remote.cache_slug)
- end
-
- def default_cache_path_for_rubygems_dir
- "#{Bundler.bundle_path}/cache"
- end
-
def _remote_specification
@_remote_specification ||= @spec_fetcher.fetch_spec([@name, @version, @original_platform])
@_remote_specification || raise(GemspecError, "Gemspec data for #{full_name} was" \
diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb
index 22e3185b7f..0a289c416f 100644
--- a/lib/bundler/rubygems_gem_installer.rb
+++ b/lib/bundler/rubygems_gem_installer.rb
@@ -61,7 +61,7 @@ module Bundler
end
def pre_install_checks
- super && validate_bundler_checksum(options[:bundler_expected_checksum])
+ super && validate_bundler_checksum(options[:bundler_checksum_store])
end
def build_extensions
@@ -115,55 +115,56 @@ module Bundler
raise DirectoryRemovalError.new(e, "Could not delete previous installation of `#{dir}`")
end
- def validate_bundler_checksum(checksum)
+ def validate_bundler_checksum(checksum_store)
return true if Bundler.settings[:disable_checksum_validation]
- return true unless checksum
+
return true unless source = @package.instance_variable_get(:@gem)
return true unless source.respond_to?(:with_read_io)
- digest = Bundler::Checksum.digest_from_file_source(source)
- calculated_checksum = send(checksum_type(checksum), digest)
+ digests = Bundler::Checksum.digests_from_file_source(source).transform_values(&:hexdigest!)
+
+ checksum = checksum_store[spec]
+ unless checksum.match_digests?(digests)
+ expected = checksum_store.send(:store)[spec.full_name]
- unless calculated_checksum == checksum
- raise SecurityError, <<-MESSAGE
+ raise SecurityError, <<~MESSAGE
Bundler cannot continue installing #{spec.name} (#{spec.version}).
The checksum for the downloaded `#{spec.full_name}.gem` does not match \
- the checksum given by the server. This means the contents of the downloaded \
- gem is different from what was uploaded to the server, and could be a potential security issue.
+ the known checksum for the gem.
+ This means the contents of the downloaded \
+ gem is different from what was uploaded to the server \
+ or first used by your teammates, and could be a potential security issue.
To resolve this issue:
- 1. delete the downloaded gem located at: `#{spec.gem_dir}/#{spec.full_name}.gem`
+ 1. delete the downloaded gem located at: `#{source.path}`
2. run `bundle install`
+ If you are sure that the new checksum is correct, you can \
+ remove the `#{GemHelpers.lock_name spec.name, spec.version, spec.platform}` entry under the lockfile `CHECKSUMS` \
+ section and rerun `bundle install`.
+
If you wish to continue installing the downloaded gem, and are certain it does not pose a \
security issue despite the mismatching checksum, do the following:
1. run `bundle config set --local disable_checksum_validation true` to turn off checksum verification
2. run `bundle install`
- (More info: The expected SHA256 checksum was #{checksum.inspect}, but the \
- checksum for the downloaded gem was #{calculated_checksum.inspect}.)
+ #{expected.map do |algo, multi|
+ next unless actual = digests[algo]
+ next if actual == multi
+
+ "(More info: The expected #{algo.upcase} checksum was #{multi.digest.inspect}, but the " \
+ "checksum for the downloaded gem was #{actual.inspect}. The expected checksum came from: #{multi.sources.join(", ")})"
+ end.compact.join("\n")}
MESSAGE
end
+ register_digests(digests, checksum_store, source)
true
end
- def checksum_type(checksum)
- case checksum.length
- when 64 then :hexdigest!
- when 44 then :base64digest!
- else raise InstallError, "The given checksum for #{spec.full_name} (#{checksum.inspect}) is not a valid SHA256 hexdigest nor base64digest"
- end
- end
-
- def hexdigest!(digest)
- digest.hexdigest!
- end
-
- def base64digest!(digest)
- if digest.respond_to?(:base64digest!)
- digest.base64digest!
- else
- [digest.digest!].pack("m0")
- end
+ def register_digests(digests, checksum_store, source)
+ checksum_store.register(
+ spec,
+ digests.map {|algo, digest| Checksum::Single.new(algo, digest, "downloaded gem @ `#{source.path}`") }
+ )
end
end
end
diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb
index f7f5ea7865..115dbd1378 100644
--- a/lib/bundler/source.rb
+++ b/lib/bundler/source.rb
@@ -11,6 +11,8 @@ module Bundler
attr_accessor :dependency_names
+ attr_reader :checksum_store
+
def unmet_deps
specs.unmet_dependency_names
end
diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb
index bdfcf8274a..cdf871250a 100644
--- a/lib/bundler/source/path.rb
+++ b/lib/bundler/source/path.rb
@@ -14,6 +14,7 @@ module Bundler
DEFAULT_GLOB = "{,*,*/*}.gemspec"
def initialize(options)
+ @checksum_store = Checksum::Store.new
@options = options.dup
@glob = options["glob"] || DEFAULT_GLOB
diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb
index 3d4d2eeec1..9ea8367006 100644
--- a/lib/bundler/source/rubygems.rb
+++ b/lib/bundler/source/rubygems.rb
@@ -19,6 +19,7 @@ module Bundler
@allow_remote = false
@allow_cached = false
@allow_local = options["allow_local"] || false
+ @checksum_store = Checksum::Store.new
Array(options["remotes"]).reverse_each {|r| add_remote(r) }
end
@@ -177,7 +178,7 @@ module Bundler
:wrappers => true,
:env_shebang => true,
:build_args => options[:build_args],
- :bundler_expected_checksum => spec.respond_to?(:checksum) && spec.checksum,
+ :bundler_checksum_store => spec.source.checksum_store,
:bundler_extension_cache_path => extension_cache_path(spec)
)
diff --git a/lib/bundler/stub_specification.rb b/lib/bundler/stub_specification.rb
index 0ce68b964c..da830cf8d4 100644
--- a/lib/bundler/stub_specification.rb
+++ b/lib/bundler/stub_specification.rb
@@ -93,16 +93,6 @@ module Bundler
stub.raw_require_paths
end
- def add_checksum(checksum)
- @checksum ||= checksum
- end
-
- def to_checksum
- return Bundler::Checksum.new(name, version, platform, ["sha256-#{checksum}"]) if checksum
-
- _remote_specification&.to_checksum
- end
-
private
def _remote_specification