Verify appcast checkpoint when running audit --download (#22565)
This commit is contained in:
parent
f2a7e61ad7
commit
65d518000d
|
@ -1,14 +1,16 @@
|
|||
require 'hbc/checkable'
|
||||
require 'hbc/download'
|
||||
require 'digest'
|
||||
|
||||
class Hbc::Audit
|
||||
include Hbc::Checkable
|
||||
|
||||
attr_reader :cask, :download
|
||||
|
||||
def initialize(cask, download = false)
|
||||
def initialize(cask, download = false, command = Hbc::SystemCommand)
|
||||
@cask = cask
|
||||
@download = download
|
||||
@command = command
|
||||
end
|
||||
|
||||
def run!
|
||||
|
@ -75,7 +77,7 @@ class Hbc::Audit
|
|||
odebug "Verifying #{stanza} string is a legal SHA-256 digest"
|
||||
if sha256.kind_of?(String)
|
||||
unless sha256.length == 64 && sha256[/^[0-9a-f]+$/i]
|
||||
add_error "sha256 string must be of 64 hexadecimal characters"
|
||||
add_error "#{stanza} string must be of 64 hexadecimal characters"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -84,7 +86,7 @@ class Hbc::Audit
|
|||
odebug "Verifying #{stanza} is not a known invalid value"
|
||||
empty_sha256 = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
|
||||
if sha256 == empty_sha256
|
||||
add_error "cannot use the sha256 for an empty string: #{empty_sha256}"
|
||||
add_error "cannot use the sha256 for an empty string in #{stanza}: #{empty_sha256}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -95,13 +97,44 @@ class Hbc::Audit
|
|||
return unless cask.appcast.checkpoint
|
||||
check_sha256_actually_256(sha256: cask.appcast.checkpoint, stanza: 'appcast :checkpoint')
|
||||
check_sha256_invalid(sha256: cask.appcast.checkpoint, stanza: 'appcast :checkpoint')
|
||||
return unless download
|
||||
check_appcast_http_code
|
||||
check_appcast_checkpoint_accuracy
|
||||
end
|
||||
|
||||
def check_appcast_has_checkpoint
|
||||
odebug 'Verifying appcast has :sha256 key'
|
||||
odebug 'Verifying appcast has :checkpoint key'
|
||||
add_error 'a checkpoint sha256 is required for appcast' unless cask.appcast.checkpoint
|
||||
end
|
||||
|
||||
def check_appcast_http_code
|
||||
odebug 'Verifying appcast returns 200 HTTP response code'
|
||||
result = @command.run('curl', args: ['--compressed', '--location', '--output', '/dev/null', '--write-out', '%{http_code}', cask.appcast], print_stderr: false)
|
||||
if result.success?
|
||||
http_code = result.stdout.chomp
|
||||
add_warning "unexpected HTTP response code retrieving appcast: #{http_code}" unless http_code == '200'
|
||||
else
|
||||
add_warning "error retrieving appcast: #{result.stderr}"
|
||||
end
|
||||
end
|
||||
|
||||
def check_appcast_checkpoint_accuracy
|
||||
odebug 'Verifying appcast checkpoint is accurate'
|
||||
result = @command.run('curl', args: ['--compressed', '--location', cask.appcast], print_stderr: false)
|
||||
if result.success?
|
||||
processed_appcast_text = result.stdout.gsub(%r{<pubDate>[^<]*</pubDate>}, '')
|
||||
expected = cask.appcast.checkpoint
|
||||
actual = Digest::SHA2.hexdigest(processed_appcast_text)
|
||||
add_warning <<-EOS.undent unless expected == actual
|
||||
appcast checkpoint mismatch
|
||||
Expected: #{expected}
|
||||
Actual: #{actual}
|
||||
EOS
|
||||
else
|
||||
add_warning "error retrieving appcast: #{result.stderr}"
|
||||
end
|
||||
end
|
||||
|
||||
def check_url
|
||||
return unless cask.url
|
||||
check_download_url_format
|
||||
|
@ -150,7 +183,7 @@ class Hbc::Audit
|
|||
end
|
||||
|
||||
def check_download
|
||||
return unless download
|
||||
return unless download && cask.url
|
||||
odebug "Auditing download"
|
||||
downloaded_path = download.perform
|
||||
Hbc::Verify.all(cask, downloaded_path)
|
||||
|
|
|
@ -2,10 +2,12 @@ require 'spec_helper'
|
|||
|
||||
describe Hbc::Audit do
|
||||
include AuditMatchers
|
||||
include Sha256Helper
|
||||
|
||||
let(:cask) { instance_double(Hbc::Cask) }
|
||||
let(:download) { false }
|
||||
let(:audit) { Hbc::Audit.new(cask, download) }
|
||||
let(:fake_system_command) { class_double(Hbc::SystemCommand) }
|
||||
let(:audit) { Hbc::Audit.new(cask, download, fake_system_command) }
|
||||
|
||||
describe "#result" do
|
||||
subject { audit.result }
|
||||
|
@ -81,18 +83,104 @@ describe Hbc::Audit do
|
|||
describe "appcast checks" do
|
||||
context "when appcast has no sha256" do
|
||||
let(:cask_token) { 'appcast-missing-checkpoint' }
|
||||
it { should fail_with('a checkpoint sha256 is required for appcast') }
|
||||
it { should fail_with(/checkpoint sha256 is required for appcast/) }
|
||||
end
|
||||
|
||||
context "when appcast checkpoint is not a string of 64 hexadecimal characters" do
|
||||
let(:cask_token) { 'appcast-invalid-checkpoint' }
|
||||
it { should fail_with('sha256 string must be of 64 hexadecimal characters') }
|
||||
it { should fail_with(/string must be of 64 hexadecimal characters/) }
|
||||
end
|
||||
|
||||
context "when appcast checkpoint is sha256 for empty string" do
|
||||
let(:cask_token) { 'appcast-checkpoint-sha256-for-empty-string' }
|
||||
it { should fail_with(/cannot use the sha256 for an empty string/) }
|
||||
end
|
||||
|
||||
context "when appcast checkpoint is valid sha256" do
|
||||
let(:cask_token) { 'appcast-valid-checkpoint' }
|
||||
it { should_not fail_with(/appcast :checkpoint/) }
|
||||
end
|
||||
|
||||
context "when verifying appcast HTTP code" do
|
||||
let(:cask_token) { 'appcast-valid-checkpoint' }
|
||||
let(:download) { instance_double(Hbc::Download) }
|
||||
let(:wrong_code_msg) { /unexpected HTTP response code/ }
|
||||
let(:curl_error_msg) { /error retrieving appcast/ }
|
||||
let(:fake_curl_result) { instance_double(Hbc::SystemCommand::Result) }
|
||||
|
||||
before do
|
||||
allow(audit).to receive(:check_appcast_checkpoint_accuracy)
|
||||
allow(fake_system_command).to receive(:run).and_return(fake_curl_result)
|
||||
allow(fake_curl_result).to receive(:success?).and_return(success)
|
||||
end
|
||||
|
||||
context "when curl succeeds" do
|
||||
let(:success) { true }
|
||||
|
||||
before { allow(fake_curl_result).to receive(:stdout).and_return(stdout) }
|
||||
|
||||
context "when HTTP code is 200" do
|
||||
let(:stdout) { '200' }
|
||||
it { should_not warn_with(wrong_code_msg) }
|
||||
end
|
||||
|
||||
context "when HTTP code is not 200" do
|
||||
let(:stdout) { '404' }
|
||||
it { should warn_with(wrong_code_msg) }
|
||||
end
|
||||
end
|
||||
|
||||
context "when curl fails" do
|
||||
let(:success) { false }
|
||||
before { allow(fake_curl_result).to receive(:stderr).and_return('Some curl error') }
|
||||
it { should warn_with(curl_error_msg) }
|
||||
end
|
||||
end
|
||||
|
||||
context "when verifying appcast checkpoint" do
|
||||
let(:cask_token) { 'appcast-valid-checkpoint' }
|
||||
let(:download) { instance_double(Hbc::Download) }
|
||||
let(:mismatch_msg) { /appcast checkpoint mismatch/ }
|
||||
let(:curl_error_msg) { /error retrieving appcast/ }
|
||||
let(:fake_curl_result) { instance_double(Hbc::SystemCommand::Result) }
|
||||
let(:expected_checkpoint) { 'd5b2dfbef7ea28c25f7a77cd7fa14d013d82b626db1d82e00e25822464ba19e2' }
|
||||
|
||||
before do
|
||||
allow(audit).to receive(:check_appcast_http_code)
|
||||
allow(fake_system_command).to receive(:run).and_return(fake_curl_result)
|
||||
allow(fake_curl_result).to receive(:success?).and_return(success)
|
||||
end
|
||||
|
||||
context "when appcast download succeeds" do
|
||||
let(:success) { true }
|
||||
let(:appcast_text) { instance_double(::String) }
|
||||
|
||||
before do
|
||||
allow(fake_curl_result).to receive(:stdout).and_return(appcast_text)
|
||||
allow(appcast_text).to receive(:gsub)
|
||||
allow(Digest::SHA2).to receive(:hexdigest).and_return(actual_checkpoint)
|
||||
end
|
||||
|
||||
context "when appcast checkpoint is out of date" do
|
||||
let(:actual_checkpoint) { random_sha256 }
|
||||
it { should warn_with(mismatch_msg) }
|
||||
it { should_not warn_with(curl_error_msg) }
|
||||
end
|
||||
|
||||
context "when appcast checkpoint is up to date" do
|
||||
let(:actual_checkpoint) { expected_checkpoint }
|
||||
it { should_not warn_with(mismatch_msg) }
|
||||
it { should_not warn_with(curl_error_msg) }
|
||||
end
|
||||
end
|
||||
|
||||
context "when appcast download fails" do
|
||||
let(:success) { false }
|
||||
before { allow(fake_curl_result).to receive(:stderr).and_return('Some curl error') }
|
||||
|
||||
it { should warn_with(curl_error_msg) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "preferred download URL formats" do
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
test_cask 'appcast-valid-checkpoint' do
|
||||
appcast 'http://localhost/appcast.xml',
|
||||
checkpoint: 'd5b2dfbef7ea28c25f7a77cd7fa14d013d82b626db1d82e00e25822464ba19e2'
|
||||
end
|
Loading…
Reference in New Issue