Refactor checksumming

Signed-off-by: Jack Nagel <jacknagel@gmail.com>
This commit is contained in:
Jack Nagel 2012-06-18 19:58:35 -05:00
parent 2c6e93cf8a
commit 76b2eee777
9 changed files with 124 additions and 84 deletions

View File

@ -22,7 +22,7 @@ def built_bottle? f
end
def bottle_current? f
f.bottle.url && f.bottle.has_checksum? && f.bottle.version == f.stable.version
f.bottle.url && !f.bottle.checksum.empty? && f.bottle.version == f.stable.version
end
def bottle_file_outdated? f, file

View File

@ -0,0 +1,22 @@
class Checksum
attr_reader :hash_type, :hexdigest
TYPES = [:md5, :sha1, :sha256]
def initialize type=:sha1, val=nil
@hash_type = type
@hexdigest = val.to_s
end
def empty?
@hexdigest.empty?
end
def to_s
@hexdigest
end
def == other
@hash_type == other.hash_type and @hexdigest == other.hexdigest
end
end

View File

@ -290,22 +290,21 @@ def audit_formula_specs f
end
end
cksum = s.checksum_type
unless cksum.nil?
hash = s.send(cksum).strip
len = case cksum
cksum = s.checksum
next if cksum.nil?
len = case cksum.hash_type
when :md5 then 32
when :sha1 then 40
when :sha256 then 64
end
if hash.empty?
problems << " * #{cksum} is empty"
else
problems << " * #{cksum} should be #{len} characters" unless hash.length == len
problems << " * #{cksum} contains invalid characters" unless hash =~ /^[a-fA-F0-9]+$/
problems << " * #{cksum} should be lowercase" unless hash == hash.downcase
end
if cksum.empty?
problems << " * #{cksum.hash_type} is empty"
else
problems << " * #{cksum.hash_type} should be #{len} characters" unless cksum.hexdigest.length == len
problems << " * #{cksum.hash_type} contains invalid characters" unless cksum.hexdigest =~ /^[a-fA-F0-9]+$/
problems << " * #{cksum.hash_type} should be lowercase" unless cksum.hexdigest == cksum.hexdigest.downcase
end
end

View File

@ -148,3 +148,29 @@ end
# raised by safe_system in utils.rb
class ErrorDuringExecution < RuntimeError
end
# raised by Pathname#verify_checksum when cksum is nil or empty
class ChecksumMissingError < ArgumentError
end
# raised by Pathname#verify_checksum when verification fails
class ChecksumMismatchError < RuntimeError
attr :advice, true
attr :expected
attr :actual
def initialize expected, actual
@expected = expected
@actual = actual
super <<-EOS.undent
#{expected.hash_type.to_s.upcase} mismatch
Expected: #{expected}
Actual: #{actual}
EOS
end
def to_s
super + advice
end
end

View File

@ -301,6 +301,13 @@ class Pathname
require 'digest/sha2'
incremental_hash(Digest::SHA2)
end
alias_method :sha256, :sha2
def verify_checksum expected
raise ChecksumMissingError if expected.nil? or expected.empty?
actual = Checksum.new(expected.hash_type, send(expected.hash_type).downcase)
raise ChecksumMismatchError.new(expected, actual) unless expected == actual
end
if '1.9' <= RUBY_VERSION
alias_method :to_str, :to_s

View File

@ -38,7 +38,7 @@ class Formula
# Ensure the bottle URL is set. If it does not have a checksum,
# then a bottle is not available for the current platform.
if @bottle and @bottle.has_checksum?
if @bottle and not (@bottle.checksum.nil? or @bottle.checksum.empty?)
@bottle.url ||= bottle_base_url + bottle_filename(self)
else
@bottle = nil
@ -514,31 +514,7 @@ public
# For FormulaInstaller.
def verify_download_integrity fn
# Checksums don't apply to HEAD
return if @active_spec == @head
type = @active_spec.checksum_type || :md5
supplied = @active_spec.send(type)
type = type.to_s.upcase
require 'digest'
hasher = Digest.const_get(type)
hash = fn.incremental_hash(hasher)
if supplied and not supplied.empty?
message = <<-EOS.undent
#{type} mismatch
Expected: #{supplied}
Got: #{hash}
Archive: #{fn}
(To retry an incomplete download, remove the file above.)
EOS
raise message unless supplied.upcase == hash.upcase
else
opoo "Cannot verify package integrity"
puts "The formula did not provide a download checksum"
puts "For your reference the #{type} is: #{hash}"
end
@active_spec.verify_download_integrity(fn)
end
private
@ -605,7 +581,7 @@ private
attr_rw :homepage, :keg_only_reason, :skip_clean_all, :cc_failures
SoftwareSpec::CHECKSUM_TYPES.each do |cksum|
Checksum::TYPES.each do |cksum|
class_eval %Q{
def #{cksum}(val=nil)
unless val.nil?

View File

@ -1,10 +1,9 @@
require 'download_strategy'
require 'checksums'
class SoftwareSpec
attr_reader :checksum, :mirrors, :specs, :strategy
CHECKSUM_TYPES = [:md5, :sha1, :sha256].freeze
VCS_SYMBOLS = {
:bzr => BazaarDownloadStrategy,
:curl => CurlDownloadStrategy,
@ -16,17 +15,6 @@ class SoftwareSpec
:svn => SubversionDownloadStrategy,
}
# Detect which type of checksum is being used, or nil if none
def checksum_type
@checksum_type ||= CHECKSUM_TYPES.detect do |type|
instance_variable_defined?("@#{type}")
end
end
def has_checksum?
(checksum_type and self.send(checksum_type)) || false
end
# Was the version defined in the DSL, or detected from the URL?
def explicit_version?
@explicit_version || false
@ -45,11 +33,29 @@ class SoftwareSpec
return detected
end
def verify_download_integrity fn
fn.verify_checksum @checksum
rescue ChecksumMissingError
opoo "Cannot verify package integrity"
puts "The formula did not provide a download checksum"
puts "For your reference the SHA1 is: #{fn.sha1}"
rescue ChecksumMismatchError => e
e.advice = <<-EOS.undent
Archive: #{fn}
(To retry an incomplete download, remove the file above.)
EOS
raise e
end
# The methods that follow are used in the block-form DSL spec methods
CHECKSUM_TYPES.each do |cksum|
Checksum::TYPES.each do |cksum|
class_eval %Q{
def #{cksum}(val=nil)
val.nil? ? @#{cksum} : @#{cksum} = val
if val.nil?
@checksum if @checksum.nil? or @checksum.hash_type == :#{cksum}
else
@checksum = Checksum.new(:#{cksum}, val)
end
end
}
end
@ -84,7 +90,6 @@ class HeadSoftwareSpec < SoftwareSpec
def initialize
super
@version = 'HEAD'
@checksum = nil
end
def verify_download_integrity fn
@ -97,13 +102,14 @@ class Bottle < SoftwareSpec
attr_reader :revision
def initialize
super
@revision = 0
@strategy = CurlBottleDownloadStrategy
end
# Checksum methods in the DSL's bottle block optionally take
# a Hash, which indicates the platform the checksum applies on.
CHECKSUM_TYPES.each do |cksum|
Checksum::TYPES.each do |cksum|
class_eval %Q{
def #{cksum}(val=nil)
@#{cksum} ||= Hash.new
@ -111,11 +117,13 @@ class Bottle < SoftwareSpec
when nil
@#{cksum}[MacOS.cat]
when String
@#{cksum}[:lion] = val
@#{cksum}[:lion] = Checksum.new(:#{cksum}, val)
when Hash
key, value = val.shift
@#{cksum}[value] = key
@#{cksum}[value] = Checksum.new(:#{cksum}, key)
end
@checksum = @#{cksum}[MacOS.cat] if @#{cksum}.has_key? MacOS.cat
end
}
end

View File

@ -13,7 +13,7 @@ class ChecksumTests < Test::Unit::TestCase
end
def bad_checksum f
assert_raises RuntimeError do
assert_raises ChecksumMismatchError do
nostdout { f.new.brew {} }
end
end

View File

@ -99,20 +99,22 @@ class FormulaTests < Test::Unit::TestCase
assert_equal CurlDownloadStrategy, f.devel.download_strategy
assert_equal GitDownloadStrategy, f.head.download_strategy
assert f.stable.has_checksum?
assert f.bottle.has_checksum?
assert f.devel.has_checksum?
assert !f.head.has_checksum?
assert_equal :sha1, f.stable.checksum_type
assert_equal :sha1, f.bottle.checksum_type
assert_equal :sha256, f.devel.checksum_type
assert_nil f.head.checksum_type
assert_instance_of Checksum, f.stable.checksum
assert_instance_of Checksum, f.bottle.checksum
assert_instance_of Checksum, f.devel.checksum
assert !f.stable.checksum.empty?
assert !f.bottle.checksum.empty?
assert !f.devel.checksum.empty?
assert_nil f.head.checksum
assert_equal :sha1, f.stable.checksum.hash_type
assert_equal :sha1, f.bottle.checksum.hash_type
assert_equal :sha256, f.devel.checksum.hash_type
assert_equal case MacOS.cat
when :snowleopard then 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef'
when :lion then 'baadf00dbaadf00dbaadf00dbaadf00dbaadf00d'
end, f.bottle.sha1
assert_match /[0-9a-fA-F]{40}/, f.stable.sha1
assert_match /[0-9a-fA-F]{64}/, f.devel.sha256
end, f.bottle.checksum.hexdigest
assert_match /[0-9a-fA-F]{40}/, f.stable.checksum.hexdigest
assert_match /[0-9a-fA-F]{64}/, f.devel.checksum.hexdigest
assert_nil f.stable.md5
assert_nil f.stable.sha256
@ -155,11 +157,10 @@ class FormulaTests < Test::Unit::TestCase
assert_nil f.stable.specs
assert_equal({ :tag => 'foo' }, f.head.specs)
assert f.stable.has_checksum?
assert !f.head.has_checksum?
assert_equal :md5, f.stable.checksum_type
assert_nil f.head.checksum_type
assert_match /[0-9a-fA-F]{32}/, f.stable.md5
assert_instance_of Checksum, f.stable.checksum
assert_nil f.head.checksum
assert_equal :md5, f.stable.checksum.hash_type
assert_match /[0-9a-fA-F]{32}/, f.stable.checksum.hexdigest
assert !f.stable.explicit_version?
assert_equal '0.1', f.stable.version
@ -209,9 +210,10 @@ class FormulaTests < Test::Unit::TestCase
assert_equal 'file:///foo.com/testball-0.1-bottle.tar.gz', f.bottle.url
assert f.bottle.has_checksum?
assert_equal :sha1, f.bottle.checksum_type
assert_equal 'baadf00dbaadf00dbaadf00dbaadf00dbaadf00d', f.bottle.sha1
assert_instance_of Checksum, f.bottle.checksum
assert_equal :sha1, f.bottle.checksum.hash_type
assert !f.bottle.checksum.empty?
assert_equal 'baadf00dbaadf00dbaadf00dbaadf00dbaadf00d', f.bottle.sha1.hexdigest
assert_nil f.bottle.md5
assert_nil f.bottle.sha256
@ -238,7 +240,7 @@ class FormulaTests < Test::Unit::TestCase
assert_equal f.head, f.active_spec
assert_equal 'HEAD', f.version
assert !f.head.has_checksum?
assert_nil f.head.checksum
assert_equal 'https://github.com/mxcl/homebrew.git', f.url
assert_equal GitDownloadStrategy, f.download_strategy
assert_instance_of GitDownloadStrategy, f.downloader
@ -255,7 +257,7 @@ class FormulaTests < Test::Unit::TestCase
assert_equal f.head, f.active_spec
assert_equal 'HEAD', f.version
assert !f.head.has_checksum?
assert_nil f.head.checksum
assert_equal 'https://github.com/mxcl/homebrew.git', f.url
assert_equal GitDownloadStrategy, f.download_strategy
assert_instance_of GitDownloadStrategy, f.downloader
@ -272,7 +274,7 @@ class FormulaTests < Test::Unit::TestCase
assert_equal f.head, f.active_spec
assert_equal 'HEAD', f.version
assert !f.head.has_checksum?
assert_nil f.head.checksum
assert_equal 'https://github.com/mxcl/homebrew.git', f.url
assert_equal GitDownloadStrategy, f.download_strategy
assert_instance_of GitDownloadStrategy, f.downloader
@ -288,9 +290,9 @@ class FormulaTests < Test::Unit::TestCase
assert_equal f.stable, f.active_spec
assert !f.stable.has_checksum?
assert !f.devel.has_checksum?
assert !f.head.has_checksum?
assert_nil f.stable.checksum
assert_nil f.devel.checksum
assert_nil f.head.checksum
assert_equal MercurialDownloadStrategy, f.stable.download_strategy
assert_equal BazaarDownloadStrategy, f.devel.download_strategy