Refactor checksumming
Signed-off-by: Jack Nagel <jacknagel@gmail.com>
This commit is contained in:
		
							parent
							
								
									2c6e93cf8a
								
							
						
					
					
						commit
						76b2eee777
					
				| @ -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 | ||||
|  | ||||
							
								
								
									
										22
									
								
								Library/Homebrew/checksums.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								Library/Homebrew/checksums.rb
									
									
									
									
									
										Normal 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 | ||||
| @ -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 | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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? | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Jack Nagel
						Jack Nagel