diff options
Diffstat (limited to 'lib/bundler')
-rw-r--r-- | lib/bundler/checksum.rb | 45 | ||||
-rw-r--r-- | lib/bundler/definition.rb | 2 | ||||
-rw-r--r-- | lib/bundler/endpoint_specification.rb | 5 | ||||
-rw-r--r-- | lib/bundler/gem_helpers.rb | 18 | ||||
-rw-r--r-- | lib/bundler/lazy_specification.rb | 54 | ||||
-rw-r--r-- | lib/bundler/lockfile_generator.rb | 15 | ||||
-rw-r--r-- | lib/bundler/lockfile_parser.rb | 5 | ||||
-rw-r--r-- | lib/bundler/remote_specification.rb | 44 | ||||
-rw-r--r-- | lib/bundler/rubygems_gem_installer.rb | 13 | ||||
-rw-r--r-- | lib/bundler/stub_specification.rb | 11 |
10 files changed, 132 insertions, 80 deletions
diff --git a/lib/bundler/checksum.rb b/lib/bundler/checksum.rb index 2e0a80cac2..0b618d5033 100644 --- a/lib/bundler/checksum.rb +++ b/lib/bundler/checksum.rb @@ -2,22 +2,37 @@ module Bundler class Checksum - attr_reader :name, :version, :platform - attr_accessor :checksum + attr_reader :name, :version, :platform, :checksums - SHA256 = /\Asha256-([a-z0-9]{64}|[A-Za-z0-9+\/=]{44})\z/.freeze + SHA256 = %r{\Asha256-([a-z0-9]{64}|[A-Za-z0-9+\/=]{44})\z}.freeze - def initialize(name, version, platform, checksum = nil) + def initialize(name, version, platform, checksums = []) @name = name @version = version @platform = platform || Gem::Platform::RUBY - @checksum = checksum + @checksums = checksums - if @checksum && @checksum !~ SHA256 - raise ArgumentError, "invalid checksum (#{@checksum})" + # can expand this validation when we support more hashing algos later + if @checksums.any? && @checksums.all? {|c| c !~ SHA256 } + raise ArgumentError, "invalid checksums (#{@checksums})" end end + def self.digest_from_file_source(file_source) + raise ArgumentError, "not a valid file source: #{file_source}" unless file_source.respond_to?(:with_read_io) + + file_source.with_read_io do |io| + digest = Bundler::SharedHelpers.digest(:SHA256).new + digest << io.read(16_384) until io.eof? + io.rewind + digest + end + end + + def full_name + GemHelpers.spec_full_name(@name, @version, @platform) + end + def match_spec?(spec) name == spec.name && version == spec.version && @@ -26,17 +41,17 @@ module Bundler def to_lock out = String.new - - if platform == Gem::Platform::RUBY - out << " #{name} (#{version})" - else - out << " #{name} (#{version}-#{platform})" - end - - out << " #{checksum}" if checksum + out << " #{GemHelpers.lock_name(name, version, platform)}" + out << " #{sha256}" if sha256 out << "\n" out end + + private + + def sha256 + @checksums.find {|c| c =~ SHA256 } + end end end diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 6b066051d8..14f6746331 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -114,7 +114,7 @@ module Bundler @originally_locked_specs = @locked_specs @locked_sources = [] @locked_platforms = [] - @locked_checksums = [] + @locked_checksums = {} end locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) } diff --git a/lib/bundler/endpoint_specification.rb b/lib/bundler/endpoint_specification.rb index 4c41285043..863544b1f9 100644 --- a/lib/bundler/endpoint_specification.rb +++ b/lib/bundler/endpoint_specification.rb @@ -104,11 +104,6 @@ module Bundler @remote_specification = spec end - def to_checksum - digest = "sha256-#{checksum}" if checksum - Bundler::Checksum.new(name, version, platform, digest) - end - private def _remote_specification diff --git a/lib/bundler/gem_helpers.rb b/lib/bundler/gem_helpers.rb index 2e6d788f9c..ed39511a10 100644 --- a/lib/bundler/gem_helpers.rb +++ b/lib/bundler/gem_helpers.rb @@ -113,5 +113,23 @@ module Bundler same_runtime_deps && same_metadata_deps end module_function :same_deps + + def spec_full_name(name, version, platform) + if platform == Gem::Platform::RUBY + "#{name}-#{version}" + else + "#{name}-#{version}-#{platform}" + end + end + module_function :spec_full_name + + def lock_name(name, version, platform) + if platform == Gem::Platform::RUBY + "#{name} (#{version})" + else + "#{name} (#{version}-#{platform})" + end + end + module_function :lock_name end end diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb index b4aadb0b5c..a17c8b90e5 100644 --- a/lib/bundler/lazy_specification.rb +++ b/lib/bundler/lazy_specification.rb @@ -20,11 +20,7 @@ module Bundler end def full_name - @full_name ||= if platform == Gem::Platform::RUBY - "#{@name}-#{@version}" - else - "#{@name}-#{@version}-#{platform}" - end + @full_name ||= GemHelpers.spec_full_name(@name, @version, platform) end def ==(other) @@ -61,12 +57,7 @@ module Bundler def to_lock out = String.new - - if platform == Gem::Platform::RUBY - out << " #{name} (#{version})\n" - else - out << " #{name} (#{version}-#{platform})\n" - end + out << " #{GemHelpers.lock_name(name, version, platform)}\n" dependencies.sort_by(&:to_s).uniq.each do |dep| next if dep.type == :development @@ -76,17 +67,18 @@ module Bundler out end - #def materialize_for_checksum - #if @specification - #yield - #else - #materialize_for_installation - - #yield + 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? - #@specification = nil - #end - #end + s = materialize_for_installation + yield s if block_given? + end def materialize_for_installation source.local! @@ -134,11 +126,7 @@ module Bundler end def to_s - @to_s ||= if platform == Gem::Platform::RUBY - "#{name} (#{version})" - else - "#{name} (#{version}-#{platform})" - end + @__to_s ||= GemHelpers.lock_name(name, version, platform) end def git_version @@ -146,20 +134,6 @@ module Bundler " #{source.revision[0..6]}" end - def to_checksum - return nil unless @specification - - # - # 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 nil unless ruby_platform_materializes_to_ruby_platform? - - @specification.to_checksum - end - private def use_exact_resolved_specifications? diff --git a/lib/bundler/lockfile_generator.rb b/lib/bundler/lockfile_generator.rb index 11e8e3f103..52b3b411aa 100644 --- a/lib/bundler/lockfile_generator.rb +++ b/lib/bundler/lockfile_generator.rb @@ -68,17 +68,14 @@ 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 - #checksum ||= spec.to_checksum if spec.respond_to?(:to_checksum) - #end - #end - - checksum ||= definition.locked_checksums.find {|c| c.match_spec?(spec) } + 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 end diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb index fc331a928c..001de06d53 100644 --- a/lib/bundler/lockfile_parser.rb +++ b/lib/bundler/lockfile_parser.rb @@ -66,7 +66,7 @@ module Bundler @sources = [] @dependencies = {} @parse_method = nil - @checksums = [] + @checksums = {} @specs = {} if lockfile.match?(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/) @@ -193,7 +193,8 @@ module Bundler version = Gem::Version.new(version) platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY - @checksums << Bundler::Checksum.new(name, version, platform, checksum) + checksum = Bundler::Checksum.new(name, version, platform, [checksum]) + @checksums[checksum.full_name] = checksum end end diff --git a/lib/bundler/remote_specification.rb b/lib/bundler/remote_specification.rb index f626a3218e..e8054dbbd5 100644 --- a/lib/bundler/remote_specification.rb +++ b/lib/bundler/remote_specification.rb @@ -93,12 +93,56 @@ 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 38035a00ac..22e3185b7f 100644 --- a/lib/bundler/rubygems_gem_installer.rb +++ b/lib/bundler/rubygems_gem_installer.rb @@ -120,13 +120,10 @@ module Bundler return true unless checksum return true unless source = @package.instance_variable_get(:@gem) return true unless source.respond_to?(:with_read_io) - digest = source.with_read_io do |io| - digest = SharedHelpers.digest(:SHA256).new - digest << io.read(16_384) until io.eof? - io.rewind - send(checksum_type(checksum), digest) - end - unless digest == checksum + digest = Bundler::Checksum.digest_from_file_source(source) + calculated_checksum = send(checksum_type(checksum), digest) + + unless calculated_checksum == checksum raise SecurityError, <<-MESSAGE Bundler cannot continue installing #{spec.name} (#{spec.version}). The checksum for the downloaded `#{spec.full_name}.gem` does not match \ @@ -143,7 +140,7 @@ module Bundler 2. run `bundle install` (More info: The expected SHA256 checksum was #{checksum.inspect}, but the \ - checksum for the downloaded gem was #{digest.inspect}.) + checksum for the downloaded gem was #{calculated_checksum.inspect}.) MESSAGE end true diff --git a/lib/bundler/stub_specification.rb b/lib/bundler/stub_specification.rb index 6f4264e561..0ce68b964c 100644 --- a/lib/bundler/stub_specification.rb +++ b/lib/bundler/stub_specification.rb @@ -9,6 +9,7 @@ module Bundler spec end + attr_reader :checksum attr_accessor :stub, :ignored def source=(source) @@ -92,6 +93,16 @@ 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 |