Merge pull request #13813 from Homebrew/revert-13746-chore/cask/cleanup_audit

Revert "Cask: automatically add cask audits"
This commit is contained in:
Mike McQuaid 2022-09-06 12:12:08 +01:00 committed by GitHub
commit ed916a5315
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 314 additions and 369 deletions

View File

@ -24,7 +24,7 @@ module Cask
def initialize(cask, appcast: nil, download: nil, quarantine: nil, def initialize(cask, appcast: nil, download: nil, quarantine: nil,
token_conflicts: nil, online: nil, strict: nil, signing: nil, token_conflicts: nil, online: nil, strict: nil, signing: nil,
new_cask: nil, only: [], except: []) new_cask: nil)
# `new_cask` implies `online`, `token_conflicts`, `strict` and `signing` # `new_cask` implies `online`, `token_conflicts`, `strict` and `signing`
online = new_cask if online.nil? online = new_cask if online.nil?
@ -47,22 +47,44 @@ module Cask
@signing = signing @signing = signing
@new_cask = new_cask @new_cask = new_cask
@token_conflicts = token_conflicts @token_conflicts = token_conflicts
@only = only
@except = except
end end
def run! def run!
only_audits = @only check_denylist
except_audits = @except check_reverse_migration
check_required_stanzas
private_methods.map(&:to_s).grep(/^check_/).each do |audit_method_name| check_version
name = audit_method_name.delete_prefix("check_") check_sha256
next if !only_audits.empty? && only_audits&.exclude?(name) check_desc
next if except_audits&.include?(name) check_url
check_unnecessary_verified
send(audit_method_name) check_missing_verified
end check_no_match
check_generic_artifacts
check_token_valid
check_token_bad_words
check_token_conflicts
check_languages
check_download
check_https_availability
check_single_pre_postflight
check_single_uninstall_zap
check_untrusted_pkg
livecheck_result = check_livecheck_version
check_hosting_with_livecheck(livecheck_result: livecheck_result)
check_appcast_and_livecheck
check_latest_with_appcast_or_livecheck
check_latest_with_auto_updates
check_stanza_requires_uninstall
check_appcast_contains_version
check_gitlab_repository
check_gitlab_repository_archived
check_gitlab_prerelease_version
check_github_repository
check_github_repository_archived
check_github_prerelease_version
check_bitbucket_repository
check_signing
self self
rescue => e rescue => e
odebug e, e.backtrace odebug e, e.backtrace
@ -78,27 +100,10 @@ module Cask
@warnings ||= [] @warnings ||= []
end end
sig { returns(T::Boolean) }
def errors?
errors.any?
end
sig { returns(T::Boolean) }
def warnings?
warnings.any?
end
sig { returns(T::Boolean) }
def success?
!(errors? || warnings?)
end
sig { params(message: String, location: T.nilable(String)).void }
def add_error(message, location: nil) def add_error(message, location: nil)
errors << ({ message: message, location: location }) errors << ({ message: message, location: location })
end end
sig { params(message: String, location: T.nilable(String)).void }
def add_warning(message, location: nil) def add_warning(message, location: nil)
if strict? if strict?
add_error message, location: location add_error message, location: location
@ -107,6 +112,14 @@ module Cask
end end
end end
def errors?
errors.any?
end
def warnings?
warnings.any?
end
def result def result
if errors? if errors?
Formatter.error("failed") Formatter.error("failed")
@ -137,9 +150,12 @@ module Cask
summary.join("\n") summary.join("\n")
end end
def success?
!(errors? || warnings?)
end
private private
sig { void }
def check_untrusted_pkg def check_untrusted_pkg
odebug "Auditing pkg stanza: allow_untrusted" odebug "Auditing pkg stanza: allow_untrusted"
@ -154,7 +170,6 @@ module Cask
add_error "allow_untrusted is not permitted in official Homebrew Cask taps" add_error "allow_untrusted is not permitted in official Homebrew Cask taps"
end end
sig { void }
def check_stanza_requires_uninstall def check_stanza_requires_uninstall
odebug "Auditing stanzas which require an uninstall" odebug "Auditing stanzas which require an uninstall"
@ -164,7 +179,6 @@ module Cask
add_error "installer and pkg stanzas require an uninstall stanza" add_error "installer and pkg stanzas require an uninstall stanza"
end end
sig { void }
def check_single_pre_postflight def check_single_pre_postflight
odebug "Auditing preflight and postflight stanzas" odebug "Auditing preflight and postflight stanzas"
@ -181,7 +195,6 @@ module Cask
add_error "only a single postflight stanza is allowed" add_error "only a single postflight stanza is allowed"
end end
sig { void }
def check_single_uninstall_zap def check_single_uninstall_zap
odebug "Auditing single uninstall_* and zap stanzas" odebug "Auditing single uninstall_* and zap stanzas"
@ -208,7 +221,6 @@ module Cask
add_error "only a single zap stanza is allowed" add_error "only a single zap stanza is allowed"
end end
sig { void }
def check_required_stanzas def check_required_stanzas
odebug "Auditing required stanzas" odebug "Auditing required stanzas"
[:version, :sha256, :url, :homepage].each do |sym| [:version, :sha256, :url, :homepage].each do |sym|
@ -221,66 +233,58 @@ module Cask
add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty? add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty?
end end
sig { void } def check_version
def check_description_present
# Fonts seldom benefit from descriptions and requiring them disproportionately increases the maintenance burden
return if cask.tap == "homebrew/cask-fonts"
add_warning "Cask should have a description. Please add a `desc` stanza." if cask.desc.blank?
end
sig { void }
def check_no_string_version_latest
return unless cask.version return unless cask.version
odebug "Auditing version :latest does not appear as a string ('latest')" check_no_string_version_latest
end
def check_no_string_version_latest
odebug "Verifying version :latest does not appear as a string ('latest')"
return unless cask.version.raw_version == "latest" return unless cask.version.raw_version == "latest"
add_error "you should use version :latest instead of version 'latest'" add_error "you should use version :latest instead of version 'latest'"
end end
sig { void } def check_sha256
def check_sha256_no_check_if_latest
return unless cask.sha256 return unless cask.sha256
odebug "Auditing sha256 :no_check with version :latest" check_sha256_no_check_if_latest
check_sha256_no_check_if_unversioned
check_sha256_actually_256
check_sha256_invalid
end
def check_sha256_no_check_if_latest
odebug "Verifying sha256 :no_check with version :latest"
return unless cask.version.latest? return unless cask.version.latest?
return if cask.sha256 == :no_check return if cask.sha256 == :no_check
add_error "you should use sha256 :no_check when version is :latest" add_error "you should use sha256 :no_check when version is :latest"
end end
sig { void }
def check_sha256_no_check_if_unversioned def check_sha256_no_check_if_unversioned
return unless cask.sha256
return if cask.sha256 == :no_check return if cask.sha256 == :no_check
add_error "Use `sha256 :no_check` when URL is unversioned." if cask.url&.unversioned? add_error "Use `sha256 :no_check` when URL is unversioned." if cask.url&.unversioned?
end end
sig { void }
def check_sha256_actually_256 def check_sha256_actually_256
return unless cask.sha256 odebug "Verifying sha256 string is a legal SHA-256 digest"
odebug "Auditing sha256 string is a legal SHA-256 digest"
return unless cask.sha256.is_a?(Checksum) return unless cask.sha256.is_a?(Checksum)
return if cask.sha256.length == 64 && cask.sha256[/^[0-9a-f]+$/i] return if cask.sha256.length == 64 && cask.sha256[/^[0-9a-f]+$/i]
add_error "sha256 string must be of 64 hexadecimal characters" add_error "sha256 string must be of 64 hexadecimal characters"
end end
sig { void }
def check_sha256_invalid def check_sha256_invalid
return unless cask.sha256 odebug "Verifying sha256 is not a known invalid value"
odebug "Auditing sha256 is not a known invalid value"
empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
return unless cask.sha256 == empty_sha256 return unless cask.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: #{empty_sha256}"
end end
sig { void }
def check_appcast_and_livecheck def check_appcast_and_livecheck
return unless cask.appcast return unless cask.appcast
@ -291,7 +295,6 @@ module Cask
end end
end end
sig { void }
def check_latest_with_appcast_or_livecheck def check_latest_with_appcast_or_livecheck
return unless cask.version.latest? return unless cask.version.latest?
@ -299,7 +302,6 @@ module Cask
add_error "Casks with a `livecheck` should not use `version :latest`." if cask.livecheckable? add_error "Casks with a `livecheck` should not use `version :latest`." if cask.livecheckable?
end end
sig { void }
def check_latest_with_auto_updates def check_latest_with_auto_updates
return unless cask.version.latest? return unless cask.version.latest?
return unless cask.auto_updates return unless cask.auto_updates
@ -309,8 +311,7 @@ module Cask
LIVECHECK_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#stanza-livecheck" LIVECHECK_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#stanza-livecheck"
sig { params(livecheck_result: T::Boolean).void } def check_hosting_with_livecheck(livecheck_result:)
def check_hosting_with_livecheck(livecheck_result: check_livecheck_version)
return if cask.discontinued? || cask.version.latest? return if cask.discontinued? || cask.version.latest?
return if block_url_offline? || cask.appcast || cask.livecheckable? return if block_url_offline? || cask.appcast || cask.livecheckable?
return if livecheck_result == :auto_detected return if livecheck_result == :auto_detected
@ -329,12 +330,24 @@ module Cask
end end
end end
SOURCEFORGE_OSDN_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#sourceforgeosdn-urls" def check_desc
return if cask.desc.present?
sig { void } # Fonts seldom benefit from descriptions and requiring them disproportionately increases the maintenance burden
def check_download_url_format return if cask.tap == "homebrew/cask-fonts"
add_warning "Cask should have a description. Please add a `desc` stanza."
end
def check_url
return unless cask.url return unless cask.url
check_download_url_format
end
SOURCEFORGE_OSDN_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#sourceforgeosdn-urls"
def check_download_url_format
odebug "Auditing URL format" odebug "Auditing URL format"
if bad_sourceforge_url? if bad_sourceforge_url?
add_error "SourceForge URL format incorrect. See #{Formatter.url(SOURCEFORGE_OSDN_REFERENCE_URL)}" add_error "SourceForge URL format incorrect. See #{Formatter.url(SOURCEFORGE_OSDN_REFERENCE_URL)}"
@ -343,9 +356,82 @@ module Cask
end end
end end
def bad_url_format?(regex, valid_formats_array)
return false unless cask.url.to_s.match?(regex)
valid_formats_array.none? { |format| cask.url.to_s =~ format }
end
def bad_sourceforge_url?
bad_url_format?(/sourceforge/,
[
%r{\Ahttps://sourceforge\.net/projects/[^/]+/files/latest/download\Z},
%r{\Ahttps://downloads\.sourceforge\.net/(?!(project|sourceforge)/)},
])
end
def bad_osdn_url?
bad_url_format?(/osd/, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}])
end
def homepage
URI(cask.homepage.to_s).host
end
def domain
URI(cask.url.to_s).host
end
def url_match_homepage?
host = cask.url.to_s
host_uri = URI(host)
host = if host.match?(/:\d/) && host_uri.port != 80
"#{host_uri.host}:#{host_uri.port}"
else
host_uri.host
end
home = homepage.downcase
if (split_host = host.split(".")).length >= 3
host = split_host[-2..].join(".")
end
if (split_home = homepage.split(".")).length >= 3
home = split_home[-2..].join(".")
end
host == home
end
def strip_url_scheme(url)
url.sub(%r{^[^:/]+://(www\.)?}, "")
end
def url_from_verified
strip_url_scheme(cask.url.verified)
end
def verified_matches_url?
url_domain, url_path = strip_url_scheme(cask.url.to_s).split("/", 2)
verified_domain, verified_path = url_from_verified.split("/", 2)
(url_domain == verified_domain || (verified_domain && url_domain&.end_with?(".#{verified_domain}"))) &&
(!verified_path || url_path&.start_with?(verified_path))
end
def verified_present?
cask.url.verified.present?
end
def file_url?
URI(cask.url.to_s).scheme == "file"
end
def block_url_offline?
return false if online?
cask.url.from_block?
end
VERIFIED_URL_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#when-url-and-homepage-domains-differ-add-verified" VERIFIED_URL_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#when-url-and-homepage-domains-differ-add-verified"
sig { void }
def check_unnecessary_verified def check_unnecessary_verified
return if block_url_offline? return if block_url_offline?
return unless verified_present? return unless verified_present?
@ -357,7 +443,6 @@ module Cask
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}" "See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
end end
sig { void }
def check_missing_verified def check_missing_verified
return if block_url_offline? return if block_url_offline?
return if file_url? return if file_url?
@ -369,7 +454,6 @@ module Cask
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}" "See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
end end
sig { void }
def check_no_match def check_no_match
return if block_url_offline? return if block_url_offline?
return unless verified_present? return unless verified_present?
@ -380,7 +464,6 @@ module Cask
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}" "See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
end end
sig { void }
def check_generic_artifacts def check_generic_artifacts
cask.artifacts.select { |a| a.is_a?(Artifact::Artifact) }.each do |artifact| cask.artifacts.select { |a| a.is_a?(Artifact::Artifact) }.each do |artifact|
unless artifact.target.absolute? unless artifact.target.absolute?
@ -389,7 +472,6 @@ module Cask
end end
end end
sig { void }
def check_languages def check_languages
@cask.languages.each do |language| @cask.languages.each do |language|
Locale.parse(language) Locale.parse(language)
@ -398,7 +480,6 @@ module Cask
end end
end end
sig { void }
def check_token_conflicts def check_token_conflicts
return unless token_conflicts? return unless token_conflicts?
return unless core_formula_names.include?(cask.token) return unless core_formula_names.include?(cask.token)
@ -407,7 +488,6 @@ module Cask
"#{Formatter.url(core_formula_url)}" "#{Formatter.url(core_formula_url)}"
end end
sig { void }
def check_token_valid def check_token_valid
add_error "cask token contains non-ascii characters" unless cask.token.ascii_only? add_error "cask token contains non-ascii characters" unless cask.token.ascii_only?
add_error "cask token + should be replaced by -plus-" if cask.token.include? "+" add_error "cask token + should be replaced by -plus-" if cask.token.include? "+"
@ -425,7 +505,6 @@ module Cask
add_error "cask token should not have leading or trailing hyphens" add_error "cask token should not have leading or trailing hyphens"
end end
sig { void }
def check_token_bad_words def check_token_bad_words
return unless new_cask? return unless new_cask?
@ -453,7 +532,19 @@ module Cask
add_warning "cask token mentions framework" add_warning "cask token mentions framework"
end end
sig { void } def core_tap
@core_tap ||= CoreTap.instance
end
def core_formula_names
core_tap.formula_names
end
sig { returns(String) }
def core_formula_url
"#{core_tap.default_remote}/blob/HEAD/Formula/#{cask.token}.rb"
end
def check_download def check_download
return if download.blank? || cask.url.blank? return if download.blank? || cask.url.blank?
@ -463,11 +554,11 @@ module Cask
add_error "download not possible: #{e}" add_error "download not possible: #{e}"
end end
sig { void }
def check_signing def check_signing
return if !signing? || download.blank? || cask.url.blank? return if !signing? || download.blank? || cask.url.blank?
odebug "Auditing signing" odebug "Auditing signing"
odebug cask.artifacts
artifacts = cask.artifacts.select { |k| k.is_a?(Artifact::Pkg) || k.is_a?(Artifact::App) } artifacts = cask.artifacts.select { |k| k.is_a?(Artifact::Pkg) || k.is_a?(Artifact::App) }
return if artifacts.empty? return if artifacts.empty?
@ -507,7 +598,6 @@ module Cask
end end
end end
sig { returns(T.nilable(T.any(T::Boolean, Symbol))) }
def check_livecheck_version def check_livecheck_version
return unless appcast? return unless appcast?
@ -545,7 +635,6 @@ module Cask
false false
end end
sig { void }
def check_appcast_contains_version def check_appcast_contains_version
return unless appcast? return unless appcast?
return if cask.appcast.to_s.empty? return if cask.appcast.to_s.empty?
@ -572,7 +661,6 @@ module Cask
EOS EOS
end end
sig { void }
def check_github_prerelease_version def check_github_prerelease_version
return if cask.tap == "homebrew/cask-versions" return if cask.tap == "homebrew/cask-versions"
@ -586,7 +674,6 @@ module Cask
add_error error if error add_error error if error
end end
sig { void }
def check_gitlab_prerelease_version def check_gitlab_prerelease_version
return if cask.tap == "homebrew/cask-versions" return if cask.tap == "homebrew/cask-versions"
@ -601,7 +688,6 @@ module Cask
add_error error if error add_error error if error
end end
sig { void }
def check_github_repository_archived def check_github_repository_archived
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if online? user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if online?
return if user.nil? return if user.nil?
@ -622,7 +708,6 @@ module Cask
end end
end end
sig { void }
def check_gitlab_repository_archived def check_gitlab_repository_archived
user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if online? user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if online?
return if user.nil? return if user.nil?
@ -643,7 +728,6 @@ module Cask
end end
end end
sig { void }
def check_github_repository def check_github_repository
return unless new_cask? return unless new_cask?
@ -656,7 +740,6 @@ module Cask
add_error error if error add_error error if error
end end
sig { void }
def check_gitlab_repository def check_gitlab_repository
return unless new_cask? return unless new_cask?
@ -669,7 +752,6 @@ module Cask
add_error error if error add_error error if error
end end
sig { void }
def check_bitbucket_repository def check_bitbucket_repository
return unless new_cask? return unless new_cask?
@ -682,62 +764,6 @@ module Cask
add_error error if error add_error error if error
end end
sig { void }
def check_denylist
return unless cask.tap
return unless cask.tap.official?
return unless (reason = Denylist.reason(cask.token))
add_error "#{cask.token} is not allowed: #{reason}"
end
sig { void }
def check_reverse_migration
return unless new_cask?
return unless cask.tap
return unless cask.tap.official?
return unless cask.tap.tap_migrations.key?(cask.token)
add_error "#{cask.token} is listed in tap_migrations.json"
end
sig { void }
def check_https_availability
return unless download
if cask.url && !cask.url.using
validate_url_for_https_availability(cask.url, "binary URL", cask.token, cask.tap,
user_agents: [cask.url.user_agent])
end
if cask.appcast && appcast?
validate_url_for_https_availability(cask.appcast, "appcast URL", cask.token, cask.tap, check_content: true)
end
return unless cask.homepage
validate_url_for_https_availability(cask.homepage, "homepage URL", cask.token, cask.tap,
user_agents: [:browser, :default],
check_content: true,
strict: strict?)
end
sig {
params(url_to_check: T.any(String, URL), url_type: String, cask_token: String, tap: Tap,
options: T.untyped).void
}
def validate_url_for_https_availability(url_to_check, url_type, cask_token, tap, **options)
problem = curl_check_http_content(url_to_check.to_s, url_type, **options)
exception = tap&.audit_exception(:secure_connection_audit_skiplist, cask_token, url_to_check.to_s)
if problem
add_error problem unless exception
elsif exception
add_error "#{url_to_check} is in the secure connection audit skiplist but does not need to be skipped"
end
end
sig { params(regex: String).returns(T.nilable(T::Array[String])) }
def get_repo_data(regex) def get_repo_data(regex)
return unless online? return unless online?
@ -751,105 +777,52 @@ module Cask
[user, repo] [user, repo]
end end
sig { params(regex: T.any(String, Regexp), valid_formats_array: T::Array[String]).returns(T::Boolean) } def check_denylist
def bad_url_format?(regex, valid_formats_array) return unless cask.tap
return false unless cask.url.to_s.match?(regex) return unless cask.tap.official?
return unless (reason = Denylist.reason(cask.token))
valid_formats_array.none? { |format| cask.url.to_s =~ format } add_error "#{cask.token} is not allowed: #{reason}"
end end
sig { returns(T::Boolean) } def check_reverse_migration
def bad_sourceforge_url? return unless new_cask?
bad_url_format?(/sourceforge/, return unless cask.tap
[ return unless cask.tap.official?
%r{\Ahttps://sourceforge\.net/projects/[^/]+/files/latest/download\Z}, return unless cask.tap.tap_migrations.key?(cask.token)
%r{\Ahttps://downloads\.sourceforge\.net/(?!(project|sourceforge)/)},
]) add_error "#{cask.token} is listed in tap_migrations.json"
end end
sig { returns(T::Boolean) } def check_https_availability
def bad_osdn_url? return unless download
bad_url_format?(/osd/, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}])
end
sig { returns(String) } if cask.url && !cask.url.using
def homepage check_url_for_https_availability(cask.url, "binary URL", cask.token, cask.tap,
URI(cask.homepage.to_s).host user_agents: [cask.url.user_agent])
end
sig { returns(String) }
def domain
URI(cask.url.to_s).host
end
sig { returns(T::Boolean) }
def url_match_homepage?
host = cask.url.to_s
host_uri = URI(host)
host = if host.match?(/:\d/) && host_uri.port != 80
"#{host_uri.host}:#{host_uri.port}"
else
host_uri.host
end end
home = homepage.downcase
if (split_host = host.split(".")).length >= 3 if cask.appcast && appcast?
host = split_host[-2..].join(".") check_url_for_https_availability(cask.appcast, "appcast URL", cask.token, cask.tap, check_content: true)
end end
if (split_home = homepage.split(".")).length >= 3
home = split_home[-2..].join(".") return unless cask.homepage
check_url_for_https_availability(cask.homepage, "homepage URL", cask.token, cask.tap,
user_agents: [:browser, :default],
check_content: true,
strict: strict?)
end
def check_url_for_https_availability(url_to_check, url_type, cask_token, tap, **options)
problem = curl_check_http_content(url_to_check.to_s, url_type, **options)
exception = tap&.audit_exception(:secure_connection_audit_skiplist, cask_token, url_to_check.to_s)
if problem
add_error problem unless exception
elsif exception
add_error "#{url_to_check} is in the secure connection audit skiplist but does not need to be skipped"
end end
host == home
end
sig { params(url: String).returns(String) }
def strip_url_scheme(url)
url.sub(%r{^[^:/]+://(www\.)?}, "")
end
sig { returns(String) }
def url_from_verified
strip_url_scheme(cask.url.verified)
end
sig { returns(T::Boolean) }
def verified_matches_url?
url_domain, url_path = strip_url_scheme(cask.url.to_s).split("/", 2)
verified_domain, verified_path = url_from_verified.split("/", 2)
(url_domain == verified_domain || (verified_domain && url_domain&.end_with?(".#{verified_domain}"))) &&
(!verified_path || url_path&.start_with?(verified_path))
end
sig { returns(T::Boolean) }
def verified_present?
cask.url.verified.present?
end
sig { returns(T::Boolean) }
def file_url?
URI(cask.url.to_s).scheme == "file"
end
sig { returns(T::Boolean) }
def block_url_offline?
return false if online?
cask.url.from_block?
end
sig { returns(Tap) }
def core_tap
@core_tap ||= CoreTap.instance
end
sig { returns(T::Array[String]) }
def core_formula_names
core_tap.formula_names
end
sig { returns(String) }
def core_formula_url
"#{core_tap.default_remote}/blob/HEAD/Formula/#{cask.token}.rb"
end end
end end
end end

View File

@ -144,6 +144,7 @@ module Cask
quarantine: @quarantine, quarantine: @quarantine,
) )
audit.run! audit.run!
audit
end end
def language_blocks def language_blocks

View File

@ -33,15 +33,13 @@ describe Cask::Audit, :cask do
let(:cask) { instance_double(Cask::Cask) } let(:cask) { instance_double(Cask::Cask) }
let(:new_cask) { nil } let(:new_cask) { nil }
let(:online) { nil } let(:online) { nil }
let(:only) { [] }
let(:strict) { nil } let(:strict) { nil }
let(:token_conflicts) { nil } let(:token_conflicts) { nil }
let(:audit) { let(:audit) {
described_class.new(cask, online: online, described_class.new(cask, online: online,
strict: strict, strict: strict,
new_cask: new_cask, new_cask: new_cask,
token_conflicts: token_conflicts, token_conflicts: token_conflicts)
only: only)
} }
describe "#new" do describe "#new" do
@ -134,7 +132,6 @@ describe Cask::Audit, :cask do
describe "token validation" do describe "token validation" do
let(:strict) { true } let(:strict) { true }
let(:only) { ["token_valid"] }
let(:cask) do let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY tmp_cask cask_token.to_s, <<~RUBY
cask '#{cask_token}' do cask '#{cask_token}' do
@ -231,7 +228,6 @@ describe Cask::Audit, :cask do
describe "token bad words" do describe "token bad words" do
let(:new_cask) { true } let(:new_cask) { true }
let(:only) { ["token_bad_words", "reverse_migration"] }
let(:online) { false } let(:online) { false }
let(:cask) do let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY tmp_cask cask_token.to_s, <<~RUBY
@ -347,7 +343,6 @@ describe Cask::Audit, :cask do
end end
describe "locale validation" do describe "locale validation" do
let(:only) { ["languages"] }
let(:cask) do let(:cask) do
tmp_cask "locale-cask-test", <<~RUBY tmp_cask "locale-cask-test", <<~RUBY
cask 'locale-cask-test' do cask 'locale-cask-test' do
@ -395,7 +390,6 @@ describe Cask::Audit, :cask do
end end
describe "pkg allow_untrusted checks" do describe "pkg allow_untrusted checks" do
let(:only) { ["untrusted_pkg"] }
let(:message) { "allow_untrusted is not permitted in official Homebrew Cask taps" } let(:message) { "allow_untrusted is not permitted in official Homebrew Cask taps" }
context "when the Cask has no pkg stanza" do context "when the Cask has no pkg stanza" do
@ -418,7 +412,6 @@ describe Cask::Audit, :cask do
end end
describe "signing checks" do describe "signing checks" do
let(:only) { ["signing"] }
let(:download_double) { instance_double(Cask::Download) } let(:download_double) { instance_double(Cask::Download) }
let(:unpack_double) { instance_double(UnpackStrategy::Zip) } let(:unpack_double) { instance_double(UnpackStrategy::Zip) }
@ -466,7 +459,6 @@ describe Cask::Audit, :cask do
end end
describe "livecheck should be skipped" do describe "livecheck should be skipped" do
let(:only) { ["livecheck_version"] }
let(:online) { true } let(:online) { true }
let(:message) { /Version '[^']*' differs from '[^']*' retrieved by livecheck\./ } let(:message) { /Version '[^']*' differs from '[^']*' retrieved by livecheck\./ }
@ -520,7 +512,6 @@ describe Cask::Audit, :cask do
end end
describe "when the Cask stanza requires uninstall" do describe "when the Cask stanza requires uninstall" do
let(:only) { ["stanza_requires_uninstall"] }
let(:message) { "installer and pkg stanzas require an uninstall stanza" } let(:message) { "installer and pkg stanzas require an uninstall stanza" }
context "when the Cask does not require an uninstall" do context "when the Cask does not require an uninstall" do
@ -690,14 +681,12 @@ describe Cask::Audit, :cask do
let(:message) { "you should use version :latest instead of version 'latest'" } let(:message) { "you should use version :latest instead of version 'latest'" }
context "when version is 'latest'" do context "when version is 'latest'" do
let(:only) { ["no_string_version_latest"] }
let(:cask_token) { "version-latest-string" } let(:cask_token) { "version-latest-string" }
it { is_expected.to fail_with(message) } it { is_expected.to fail_with(message) }
end end
context "when version is :latest" do context "when version is :latest" do
let(:only) { ["sha256_no_check_if_latest"] }
let(:cask_token) { "version-latest-with-checksum" } let(:cask_token) { "version-latest-with-checksum" }
it { is_expected.not_to fail_with(message) } it { is_expected.not_to fail_with(message) }
@ -706,21 +695,18 @@ describe Cask::Audit, :cask do
describe "sha256 checks" do describe "sha256 checks" do
context "when version is :latest and sha256 is not :no_check" do context "when version is :latest and sha256 is not :no_check" do
let(:only) { ["sha256_no_check_if_latest"] }
let(:cask_token) { "version-latest-with-checksum" } let(:cask_token) { "version-latest-with-checksum" }
it { is_expected.to fail_with("you should use sha256 :no_check when version is :latest") } it { is_expected.to fail_with("you should use sha256 :no_check when version is :latest") }
end end
context "when sha256 is not a legal SHA-256 digest" do context "when sha256 is not a legal SHA-256 digest" do
let(:only) { ["sha256_actually_256"] }
let(:cask_token) { "invalid-sha256" } let(:cask_token) { "invalid-sha256" }
it { is_expected.to fail_with("sha256 string must be of 64 hexadecimal characters") } it { is_expected.to fail_with("sha256 string must be of 64 hexadecimal characters") }
end end
context "when sha256 is sha256 for empty string" do context "when sha256 is sha256 for empty string" do
let(:only) { ["sha256_invalid"] }
let(:cask_token) { "sha256-for-empty-string" } let(:cask_token) { "sha256-for-empty-string" }
it { is_expected.to fail_with(/cannot use the sha256 for an empty string/) } it { is_expected.to fail_with(/cannot use the sha256 for an empty string/) }
@ -728,7 +714,6 @@ describe Cask::Audit, :cask do
end end
describe "hosting with livecheck checks" do describe "hosting with livecheck checks" do
let(:only) { ["hosting_with_livecheck"] }
let(:message) { /please add a livecheck/ } let(:message) { /please add a livecheck/ }
context "when the download does not use hosting with a livecheck" do context "when the download does not use hosting with a livecheck" do
@ -776,7 +761,6 @@ describe Cask::Audit, :cask do
end end
describe "latest with appcast checks" do describe "latest with appcast checks" do
let(:only) { ["latest_with_appcast_or_livecheck"] }
let(:message) { "Casks with an `appcast` should not use `version :latest`." } let(:message) { "Casks with an `appcast` should not use `version :latest`." }
context "when the Cask is :latest and does not have an appcast" do context "when the Cask is :latest and does not have an appcast" do
@ -799,8 +783,6 @@ describe Cask::Audit, :cask do
end end
describe "denylist checks" do describe "denylist checks" do
let(:only) { ["denylist"] }
context "when the Cask is not on the denylist" do context "when the Cask is not on the denylist" do
let(:cask_token) { "adobe-air" } let(:cask_token) { "adobe-air" }
@ -823,7 +805,6 @@ describe Cask::Audit, :cask do
end end
describe "latest with auto_updates checks" do describe "latest with auto_updates checks" do
let(:only) { ["latest_with_auto_updates"] }
let(:message) { "Casks with `version :latest` should not use `auto_updates`." } let(:message) { "Casks with `version :latest` should not use `auto_updates`." }
context "when the Cask is :latest and does not have auto_updates" do context "when the Cask is :latest and does not have auto_updates" do
@ -852,7 +833,6 @@ describe Cask::Audit, :cask do
end end
describe "preferred download URL formats" do describe "preferred download URL formats" do
let(:only) { ["download_url_format"] }
let(:message) { /URL format incorrect/ } let(:message) { /URL format incorrect/ }
context "with incorrect SourceForge URL format" do context "with incorrect SourceForge URL format" do
@ -887,8 +867,6 @@ describe Cask::Audit, :cask do
end end
describe "generic artifact checks" do describe "generic artifact checks" do
let(:only) { ["generic_artifacts"] }
context "with relative target" do context "with relative target" do
let(:cask_token) { "generic-artifact-relative-target" } let(:cask_token) { "generic-artifact-relative-target" }
@ -909,8 +887,6 @@ describe Cask::Audit, :cask do
end end
describe "url checks" do describe "url checks" do
let(:only) { %w[unnecessary_verified missing_verified no_match] }
context "with a block" do context "with a block" do
let(:cask_token) { "booby-trap" } let(:cask_token) { "booby-trap" }
@ -924,7 +900,7 @@ describe Cask::Audit, :cask do
let(:online) { false } let(:online) { false }
it "does not evaluate the block" do it "does not evaluate the block" do
expect(run).not_to fail_with(/Boom/) expect(run).not_to pass
end end
end end
@ -939,7 +915,6 @@ describe Cask::Audit, :cask do
end end
describe "token conflicts" do describe "token conflicts" do
let(:only) { ["token_conflicts"] }
let(:cask_token) { "with-binary" } let(:cask_token) { "with-binary" }
let(:token_conflicts) { true } let(:token_conflicts) { true }
@ -960,7 +935,6 @@ describe Cask::Audit, :cask do
end end
describe "audit of downloads" do describe "audit of downloads" do
let(:only) { ["download"] }
let(:cask_token) { "basic-cask" } let(:cask_token) { "basic-cask" }
let(:cask) { Cask::CaskLoader.load(cask_token) } let(:cask) { Cask::CaskLoader.load(cask_token) }
let(:download_double) { instance_double(Cask::Download) } let(:download_double) { instance_double(Cask::Download) }
@ -985,7 +959,6 @@ describe Cask::Audit, :cask do
context "when an exception is raised" do context "when an exception is raised" do
let(:cask) { instance_double(Cask::Cask) } let(:cask) { instance_double(Cask::Cask) }
let(:only) { ["description_present"] }
it "fails the audit" do it "fails the audit" do
expect(cask).to receive(:tap).and_raise(StandardError.new) expect(cask).to receive(:tap).and_raise(StandardError.new)
@ -993,8 +966,7 @@ describe Cask::Audit, :cask do
end end
end end
describe "checking description" do describe "without description" do
let(:only) { ["description_present"] }
let(:cask_token) { "without-description" } let(:cask_token) { "without-description" }
let(:cask) do let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY tmp_cask cask_token.to_s, <<~RUBY
@ -1024,132 +996,131 @@ describe Cask::Audit, :cask do
expect(run).to warn_with(/should have a description/) expect(run).to warn_with(/should have a description/)
end end
end end
end
context "with description" do context "with description" do
let(:cask_token) { "with-description" } let(:cask_token) { "with-description" }
let(:cask) do let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY tmp_cask cask_token.to_s, <<~RUBY
cask "#{cask_token}" do cask "#{cask_token}" do
version "1.0" version "1.0"
sha256 "8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a" sha256 "8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a"
url "https://brew.sh/\#{version}.zip" url "https://brew.sh/\#{version}.zip"
name "Audit" name "Audit"
desc "Cask Auditor" desc "Cask Auditor"
homepage "https://brew.sh/" homepage "https://brew.sh/"
app "Audit.app" app "Audit.app"
end end
RUBY RUBY
end end
it "passes" do it "passes" do
expect(run).to pass expect(run).to pass
end
end end
end end
describe "checking verified" do context "when the url matches the homepage" do
let(:only) { %w[unnecessary_verified missing_verified no_match required_stanzas] }
let(:cask_token) { "foo" } let(:cask_token) { "foo" }
let(:cask) do
context "when the url matches the homepage" do tmp_cask cask_token.to_s, <<~RUBY
let(:cask) do cask '#{cask_token}' do
tmp_cask cask_token.to_s, <<~RUBY version '1.0'
cask '#{cask_token}' do sha256 '8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a'
version '1.0' url 'https://foo.brew.sh/foo.zip'
sha256 '8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a' name 'Audit'
url 'https://foo.brew.sh/foo.zip' desc 'Audit Description'
name 'Audit' homepage 'https://foo.brew.sh'
desc 'Audit Description' app 'Audit.app'
homepage 'https://foo.brew.sh' end
app 'Audit.app' RUBY
end
RUBY
end
it { is_expected.to pass }
end end
context "when the url does not match the homepage" do it { is_expected.to pass }
let(:cask) do end
tmp_cask cask_token.to_s, <<~RUBY
cask '#{cask_token}' do
version "1.8.0_72,8.13.0.5"
sha256 "8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a"
url "https://brew.sh/foo-\#{version.after_comma}.zip"
name "Audit"
desc "Audit Description"
homepage "https://foo.example.org"
app "Audit.app"
end
RUBY
end
it { is_expected.to fail_with(/a 'verified' parameter has to be added/) } context "when the url does not match the homepage" do
let(:cask_token) { "foo" }
let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY
cask '#{cask_token}' do
version "1.8.0_72,8.13.0.5"
sha256 "8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a"
url "https://brew.sh/foo-\#{version.after_comma}.zip"
name "Audit"
desc "Audit Description"
homepage "https://foo.example.org"
app "Audit.app"
end
RUBY
end end
context "when the url does not match the homepage with verified" do it { is_expected.to fail_with(/a 'verified' parameter has to be added/) }
let(:cask) do end
tmp_cask cask_token.to_s, <<~RUBY
cask "#{cask_token}" do
version "1.8.0_72,8.13.0.5"
sha256 "8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a"
url "https://brew.sh/foo-\#{version.after_comma}.zip", verified: "brew.sh"
name "Audit"
desc "Audit Description"
homepage "https://foo.example.org"
app "Audit.app"
end
RUBY
end
it { is_expected.to pass } context "when the url does not match the homepage with verified" do
let(:cask_token) { "foo" }
let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY
cask "#{cask_token}" do
version "1.8.0_72,8.13.0.5"
sha256 "8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a"
url "https://brew.sh/foo-\#{version.after_comma}.zip", verified: "brew.sh"
name "Audit"
desc "Audit Description"
homepage "https://foo.example.org"
app "Audit.app"
end
RUBY
end end
context "when there is no homepage" do it { is_expected.to pass }
let(:cask) do end
tmp_cask cask_token.to_s, <<~RUBY
cask '#{cask_token}' do
version '1.8.0_72,8.13.0.5'
sha256 '8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a'
url 'https://brew.sh/foo.zip'
name 'Audit'
desc 'Audit Description'
app 'Audit.app'
end
RUBY
end
it { is_expected.to fail_with(/a homepage stanza is required/) } context "when there is no homepage" do
let(:cask_token) { "foo" }
let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY
cask '#{cask_token}' do
version '1.8.0_72,8.13.0.5'
sha256 '8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a'
url 'https://brew.sh/foo.zip'
name 'Audit'
desc 'Audit Description'
app 'Audit.app'
end
RUBY
end end
context "when url is lazy" do it { is_expected.to fail_with(/a homepage stanza is required/) }
let(:strict) { true } end
let(:cask_token) { "with-lazy" }
let(:cask) do context "when url is lazy" do
tmp_cask cask_token.to_s, <<~RUBY let(:strict) { true }
cask '#{cask_token}' do let(:cask_token) { "with-lazy" }
version '1.8.0_72,8.13.0.5' let(:cask) do
sha256 '8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a' tmp_cask cask_token.to_s, <<~RUBY
url do cask '#{cask_token}' do
['https://brew.sh/foo.zip', {referer: 'https://example.com', cookies: {'foo' => 'bar'}}] version '1.8.0_72,8.13.0.5'
end sha256 '8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a'
name 'Audit' url do
desc 'Audit Description' ['https://brew.sh/foo.zip', {referer: 'https://example.com', cookies: {'foo' => 'bar'}}]
homepage 'https://brew.sh'
app 'Audit.app'
end end
RUBY name 'Audit'
end desc 'Audit Description'
homepage 'https://brew.sh'
app 'Audit.app'
end
RUBY
end
it { is_expected.to pass } it { is_expected.to pass }
it "receives a referer" do it "receives a referer" do
expect(audit.cask.url.referer).to eq "https://example.com" expect(audit.cask.url.referer).to eq "https://example.com"
end end
it "receives cookies" do it "receives cookies" do
expect(audit.cask.url.cookies).to eq "foo" => "bar" expect(audit.cask.url.cookies).to eq "foo" => "bar"
end
end end
end end
end end