Merge pull request #13813 from Homebrew/revert-13746-chore/cask/cleanup_audit
Revert "Cask: automatically add cask audits"
This commit is contained in:
commit
ed916a5315
@ -24,7 +24,7 @@ module Cask
|
||||
|
||||
def initialize(cask, appcast: nil, download: nil, quarantine: 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`
|
||||
online = new_cask if online.nil?
|
||||
@ -47,22 +47,44 @@ module Cask
|
||||
@signing = signing
|
||||
@new_cask = new_cask
|
||||
@token_conflicts = token_conflicts
|
||||
@only = only
|
||||
@except = except
|
||||
end
|
||||
|
||||
def run!
|
||||
only_audits = @only
|
||||
except_audits = @except
|
||||
|
||||
private_methods.map(&:to_s).grep(/^check_/).each do |audit_method_name|
|
||||
name = audit_method_name.delete_prefix("check_")
|
||||
next if !only_audits.empty? && only_audits&.exclude?(name)
|
||||
next if except_audits&.include?(name)
|
||||
|
||||
send(audit_method_name)
|
||||
end
|
||||
|
||||
check_denylist
|
||||
check_reverse_migration
|
||||
check_required_stanzas
|
||||
check_version
|
||||
check_sha256
|
||||
check_desc
|
||||
check_url
|
||||
check_unnecessary_verified
|
||||
check_missing_verified
|
||||
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
|
||||
rescue => e
|
||||
odebug e, e.backtrace
|
||||
@ -78,27 +100,10 @@ module Cask
|
||||
@warnings ||= []
|
||||
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)
|
||||
errors << ({ message: message, location: location })
|
||||
end
|
||||
|
||||
sig { params(message: String, location: T.nilable(String)).void }
|
||||
def add_warning(message, location: nil)
|
||||
if strict?
|
||||
add_error message, location: location
|
||||
@ -107,6 +112,14 @@ module Cask
|
||||
end
|
||||
end
|
||||
|
||||
def errors?
|
||||
errors.any?
|
||||
end
|
||||
|
||||
def warnings?
|
||||
warnings.any?
|
||||
end
|
||||
|
||||
def result
|
||||
if errors?
|
||||
Formatter.error("failed")
|
||||
@ -137,9 +150,12 @@ module Cask
|
||||
summary.join("\n")
|
||||
end
|
||||
|
||||
def success?
|
||||
!(errors? || warnings?)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
sig { void }
|
||||
def check_untrusted_pkg
|
||||
odebug "Auditing pkg stanza: allow_untrusted"
|
||||
|
||||
@ -154,7 +170,6 @@ module Cask
|
||||
add_error "allow_untrusted is not permitted in official Homebrew Cask taps"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_stanza_requires_uninstall
|
||||
odebug "Auditing stanzas which require an uninstall"
|
||||
|
||||
@ -164,7 +179,6 @@ module Cask
|
||||
add_error "installer and pkg stanzas require an uninstall stanza"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_single_pre_postflight
|
||||
odebug "Auditing preflight and postflight stanzas"
|
||||
|
||||
@ -181,7 +195,6 @@ module Cask
|
||||
add_error "only a single postflight stanza is allowed"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_single_uninstall_zap
|
||||
odebug "Auditing single uninstall_* and zap stanzas"
|
||||
|
||||
@ -208,7 +221,6 @@ module Cask
|
||||
add_error "only a single zap stanza is allowed"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_required_stanzas
|
||||
odebug "Auditing required stanzas"
|
||||
[: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?
|
||||
end
|
||||
|
||||
sig { void }
|
||||
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
|
||||
def check_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"
|
||||
|
||||
add_error "you should use version :latest instead of version 'latest'"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_sha256_no_check_if_latest
|
||||
def check_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 if cask.sha256 == :no_check
|
||||
|
||||
add_error "you should use sha256 :no_check when version is :latest"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_sha256_no_check_if_unversioned
|
||||
return unless cask.sha256
|
||||
return if cask.sha256 == :no_check
|
||||
|
||||
add_error "Use `sha256 :no_check` when URL is unversioned." if cask.url&.unversioned?
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_sha256_actually_256
|
||||
return unless cask.sha256
|
||||
|
||||
odebug "Auditing sha256 string is a legal SHA-256 digest"
|
||||
odebug "Verifying sha256 string is a legal SHA-256 digest"
|
||||
return unless cask.sha256.is_a?(Checksum)
|
||||
return if cask.sha256.length == 64 && cask.sha256[/^[0-9a-f]+$/i]
|
||||
|
||||
add_error "sha256 string must be of 64 hexadecimal characters"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_sha256_invalid
|
||||
return unless cask.sha256
|
||||
|
||||
odebug "Auditing sha256 is not a known invalid value"
|
||||
odebug "Verifying sha256 is not a known invalid value"
|
||||
empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
return unless cask.sha256 == empty_sha256
|
||||
|
||||
add_error "cannot use the sha256 for an empty string: #{empty_sha256}"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_appcast_and_livecheck
|
||||
return unless cask.appcast
|
||||
|
||||
@ -291,7 +295,6 @@ module Cask
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_latest_with_appcast_or_livecheck
|
||||
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?
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_latest_with_auto_updates
|
||||
return unless cask.version.latest?
|
||||
return unless cask.auto_updates
|
||||
@ -309,8 +311,7 @@ module Cask
|
||||
|
||||
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: check_livecheck_version)
|
||||
def check_hosting_with_livecheck(livecheck_result:)
|
||||
return if cask.discontinued? || cask.version.latest?
|
||||
return if block_url_offline? || cask.appcast || cask.livecheckable?
|
||||
return if livecheck_result == :auto_detected
|
||||
@ -329,12 +330,24 @@ module Cask
|
||||
end
|
||||
end
|
||||
|
||||
SOURCEFORGE_OSDN_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#sourceforgeosdn-urls"
|
||||
def check_desc
|
||||
return if cask.desc.present?
|
||||
|
||||
sig { void }
|
||||
def check_download_url_format
|
||||
# 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."
|
||||
end
|
||||
|
||||
def check_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"
|
||||
if bad_sourceforge_url?
|
||||
add_error "SourceForge URL format incorrect. See #{Formatter.url(SOURCEFORGE_OSDN_REFERENCE_URL)}"
|
||||
@ -343,9 +356,82 @@ module Cask
|
||||
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"
|
||||
|
||||
sig { void }
|
||||
def check_unnecessary_verified
|
||||
return if block_url_offline?
|
||||
return unless verified_present?
|
||||
@ -357,7 +443,6 @@ module Cask
|
||||
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_missing_verified
|
||||
return if block_url_offline?
|
||||
return if file_url?
|
||||
@ -369,7 +454,6 @@ module Cask
|
||||
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_no_match
|
||||
return if block_url_offline?
|
||||
return unless verified_present?
|
||||
@ -380,7 +464,6 @@ module Cask
|
||||
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_generic_artifacts
|
||||
cask.artifacts.select { |a| a.is_a?(Artifact::Artifact) }.each do |artifact|
|
||||
unless artifact.target.absolute?
|
||||
@ -389,7 +472,6 @@ module Cask
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_languages
|
||||
@cask.languages.each do |language|
|
||||
Locale.parse(language)
|
||||
@ -398,7 +480,6 @@ module Cask
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_token_conflicts
|
||||
return unless token_conflicts?
|
||||
return unless core_formula_names.include?(cask.token)
|
||||
@ -407,7 +488,6 @@ module Cask
|
||||
"#{Formatter.url(core_formula_url)}"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_token_valid
|
||||
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? "+"
|
||||
@ -425,7 +505,6 @@ module Cask
|
||||
add_error "cask token should not have leading or trailing hyphens"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_token_bad_words
|
||||
return unless new_cask?
|
||||
|
||||
@ -453,7 +532,19 @@ module Cask
|
||||
add_warning "cask token mentions framework"
|
||||
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
|
||||
return if download.blank? || cask.url.blank?
|
||||
|
||||
@ -463,11 +554,11 @@ module Cask
|
||||
add_error "download not possible: #{e}"
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_signing
|
||||
return if !signing? || download.blank? || cask.url.blank?
|
||||
|
||||
odebug "Auditing signing"
|
||||
odebug cask.artifacts
|
||||
artifacts = cask.artifacts.select { |k| k.is_a?(Artifact::Pkg) || k.is_a?(Artifact::App) }
|
||||
|
||||
return if artifacts.empty?
|
||||
@ -507,7 +598,6 @@ module Cask
|
||||
end
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(T.any(T::Boolean, Symbol))) }
|
||||
def check_livecheck_version
|
||||
return unless appcast?
|
||||
|
||||
@ -545,7 +635,6 @@ module Cask
|
||||
false
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_appcast_contains_version
|
||||
return unless appcast?
|
||||
return if cask.appcast.to_s.empty?
|
||||
@ -572,7 +661,6 @@ module Cask
|
||||
EOS
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_github_prerelease_version
|
||||
return if cask.tap == "homebrew/cask-versions"
|
||||
|
||||
@ -586,7 +674,6 @@ module Cask
|
||||
add_error error if error
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_gitlab_prerelease_version
|
||||
return if cask.tap == "homebrew/cask-versions"
|
||||
|
||||
@ -601,7 +688,6 @@ module Cask
|
||||
add_error error if error
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_github_repository_archived
|
||||
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if online?
|
||||
return if user.nil?
|
||||
@ -622,7 +708,6 @@ module Cask
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_gitlab_repository_archived
|
||||
user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if online?
|
||||
return if user.nil?
|
||||
@ -643,7 +728,6 @@ module Cask
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_github_repository
|
||||
return unless new_cask?
|
||||
|
||||
@ -656,7 +740,6 @@ module Cask
|
||||
add_error error if error
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_gitlab_repository
|
||||
return unless new_cask?
|
||||
|
||||
@ -669,7 +752,6 @@ module Cask
|
||||
add_error error if error
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def check_bitbucket_repository
|
||||
return unless new_cask?
|
||||
|
||||
@ -682,62 +764,6 @@ module Cask
|
||||
add_error error if error
|
||||
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)
|
||||
return unless online?
|
||||
|
||||
@ -751,105 +777,52 @@ module Cask
|
||||
[user, repo]
|
||||
end
|
||||
|
||||
sig { params(regex: T.any(String, Regexp), valid_formats_array: T::Array[String]).returns(T::Boolean) }
|
||||
def bad_url_format?(regex, valid_formats_array)
|
||||
return false unless cask.url.to_s.match?(regex)
|
||||
def check_denylist
|
||||
return unless cask.tap
|
||||
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
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def bad_sourceforge_url?
|
||||
bad_url_format?(/sourceforge/,
|
||||
[
|
||||
%r{\Ahttps://sourceforge\.net/projects/[^/]+/files/latest/download\Z},
|
||||
%r{\Ahttps://downloads\.sourceforge\.net/(?!(project|sourceforge)/)},
|
||||
])
|
||||
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 { returns(T::Boolean) }
|
||||
def bad_osdn_url?
|
||||
bad_url_format?(/osd/, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}])
|
||||
def check_https_availability
|
||||
return unless download
|
||||
|
||||
if cask.url && !cask.url.using
|
||||
check_url_for_https_availability(cask.url, "binary URL", cask.token, cask.tap,
|
||||
user_agents: [cask.url.user_agent])
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def homepage
|
||||
URI(cask.homepage.to_s).host
|
||||
if cask.appcast && appcast?
|
||||
check_url_for_https_availability(cask.appcast, "appcast URL", cask.token, cask.tap, check_content: true)
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def domain
|
||||
URI(cask.url.to_s).host
|
||||
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
|
||||
|
||||
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
|
||||
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 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)
|
||||
|
||||
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"
|
||||
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
|
||||
end
|
||||
end
|
||||
|
@ -144,6 +144,7 @@ module Cask
|
||||
quarantine: @quarantine,
|
||||
)
|
||||
audit.run!
|
||||
audit
|
||||
end
|
||||
|
||||
def language_blocks
|
||||
|
@ -33,15 +33,13 @@ describe Cask::Audit, :cask do
|
||||
let(:cask) { instance_double(Cask::Cask) }
|
||||
let(:new_cask) { nil }
|
||||
let(:online) { nil }
|
||||
let(:only) { [] }
|
||||
let(:strict) { nil }
|
||||
let(:token_conflicts) { nil }
|
||||
let(:audit) {
|
||||
described_class.new(cask, online: online,
|
||||
strict: strict,
|
||||
new_cask: new_cask,
|
||||
token_conflicts: token_conflicts,
|
||||
only: only)
|
||||
token_conflicts: token_conflicts)
|
||||
}
|
||||
|
||||
describe "#new" do
|
||||
@ -134,7 +132,6 @@ describe Cask::Audit, :cask do
|
||||
|
||||
describe "token validation" do
|
||||
let(:strict) { true }
|
||||
let(:only) { ["token_valid"] }
|
||||
let(:cask) do
|
||||
tmp_cask cask_token.to_s, <<~RUBY
|
||||
cask '#{cask_token}' do
|
||||
@ -231,7 +228,6 @@ describe Cask::Audit, :cask do
|
||||
|
||||
describe "token bad words" do
|
||||
let(:new_cask) { true }
|
||||
let(:only) { ["token_bad_words", "reverse_migration"] }
|
||||
let(:online) { false }
|
||||
let(:cask) do
|
||||
tmp_cask cask_token.to_s, <<~RUBY
|
||||
@ -347,7 +343,6 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
describe "locale validation" do
|
||||
let(:only) { ["languages"] }
|
||||
let(:cask) do
|
||||
tmp_cask "locale-cask-test", <<~RUBY
|
||||
cask 'locale-cask-test' do
|
||||
@ -395,7 +390,6 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
describe "pkg allow_untrusted checks" do
|
||||
let(:only) { ["untrusted_pkg"] }
|
||||
let(:message) { "allow_untrusted is not permitted in official Homebrew Cask taps" }
|
||||
|
||||
context "when the Cask has no pkg stanza" do
|
||||
@ -418,7 +412,6 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
describe "signing checks" do
|
||||
let(:only) { ["signing"] }
|
||||
let(:download_double) { instance_double(Cask::Download) }
|
||||
let(:unpack_double) { instance_double(UnpackStrategy::Zip) }
|
||||
|
||||
@ -466,7 +459,6 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
describe "livecheck should be skipped" do
|
||||
let(:only) { ["livecheck_version"] }
|
||||
let(:online) { true }
|
||||
let(:message) { /Version '[^']*' differs from '[^']*' retrieved by livecheck\./ }
|
||||
|
||||
@ -520,7 +512,6 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
describe "when the Cask stanza requires uninstall" do
|
||||
let(:only) { ["stanza_requires_uninstall"] }
|
||||
let(:message) { "installer and pkg stanzas require an uninstall stanza" }
|
||||
|
||||
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'" }
|
||||
|
||||
context "when version is 'latest'" do
|
||||
let(:only) { ["no_string_version_latest"] }
|
||||
let(:cask_token) { "version-latest-string" }
|
||||
|
||||
it { is_expected.to fail_with(message) }
|
||||
end
|
||||
|
||||
context "when version is :latest" do
|
||||
let(:only) { ["sha256_no_check_if_latest"] }
|
||||
let(:cask_token) { "version-latest-with-checksum" }
|
||||
|
||||
it { is_expected.not_to fail_with(message) }
|
||||
@ -706,21 +695,18 @@ describe Cask::Audit, :cask do
|
||||
|
||||
describe "sha256 checks" 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" }
|
||||
|
||||
it { is_expected.to fail_with("you should use sha256 :no_check when version is :latest") }
|
||||
end
|
||||
|
||||
context "when sha256 is not a legal SHA-256 digest" do
|
||||
let(:only) { ["sha256_actually_256"] }
|
||||
let(:cask_token) { "invalid-sha256" }
|
||||
|
||||
it { is_expected.to fail_with("sha256 string must be of 64 hexadecimal characters") }
|
||||
end
|
||||
|
||||
context "when sha256 is sha256 for empty string" do
|
||||
let(:only) { ["sha256_invalid"] }
|
||||
let(:cask_token) { "sha256-for-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
|
||||
|
||||
describe "hosting with livecheck checks" do
|
||||
let(:only) { ["hosting_with_livecheck"] }
|
||||
let(:message) { /please add a livecheck/ }
|
||||
|
||||
context "when the download does not use hosting with a livecheck" do
|
||||
@ -776,7 +761,6 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
describe "latest with appcast checks" do
|
||||
let(:only) { ["latest_with_appcast_or_livecheck"] }
|
||||
let(:message) { "Casks with an `appcast` should not use `version :latest`." }
|
||||
|
||||
context "when the Cask is :latest and does not have an appcast" do
|
||||
@ -799,8 +783,6 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
describe "denylist checks" do
|
||||
let(:only) { ["denylist"] }
|
||||
|
||||
context "when the Cask is not on the denylist" do
|
||||
let(:cask_token) { "adobe-air" }
|
||||
|
||||
@ -823,7 +805,6 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
describe "latest with auto_updates checks" do
|
||||
let(:only) { ["latest_with_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
|
||||
@ -852,7 +833,6 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
describe "preferred download URL formats" do
|
||||
let(:only) { ["download_url_format"] }
|
||||
let(:message) { /URL format incorrect/ }
|
||||
|
||||
context "with incorrect SourceForge URL format" do
|
||||
@ -887,8 +867,6 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
describe "generic artifact checks" do
|
||||
let(:only) { ["generic_artifacts"] }
|
||||
|
||||
context "with relative target" do
|
||||
let(:cask_token) { "generic-artifact-relative-target" }
|
||||
|
||||
@ -909,8 +887,6 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
describe "url checks" do
|
||||
let(:only) { %w[unnecessary_verified missing_verified no_match] }
|
||||
|
||||
context "with a block" do
|
||||
let(:cask_token) { "booby-trap" }
|
||||
|
||||
@ -924,7 +900,7 @@ describe Cask::Audit, :cask do
|
||||
let(:online) { false }
|
||||
|
||||
it "does not evaluate the block" do
|
||||
expect(run).not_to fail_with(/Boom/)
|
||||
expect(run).not_to pass
|
||||
end
|
||||
end
|
||||
|
||||
@ -939,7 +915,6 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
describe "token conflicts" do
|
||||
let(:only) { ["token_conflicts"] }
|
||||
let(:cask_token) { "with-binary" }
|
||||
let(:token_conflicts) { true }
|
||||
|
||||
@ -960,7 +935,6 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
describe "audit of downloads" do
|
||||
let(:only) { ["download"] }
|
||||
let(:cask_token) { "basic-cask" }
|
||||
let(:cask) { Cask::CaskLoader.load(cask_token) }
|
||||
let(:download_double) { instance_double(Cask::Download) }
|
||||
@ -985,7 +959,6 @@ describe Cask::Audit, :cask do
|
||||
|
||||
context "when an exception is raised" do
|
||||
let(:cask) { instance_double(Cask::Cask) }
|
||||
let(:only) { ["description_present"] }
|
||||
|
||||
it "fails the audit" do
|
||||
expect(cask).to receive(:tap).and_raise(StandardError.new)
|
||||
@ -993,8 +966,7 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
end
|
||||
|
||||
describe "checking description" do
|
||||
let(:only) { ["description_present"] }
|
||||
describe "without description" do
|
||||
let(:cask_token) { "without-description" }
|
||||
let(:cask) do
|
||||
tmp_cask cask_token.to_s, <<~RUBY
|
||||
@ -1024,6 +996,7 @@ describe Cask::Audit, :cask do
|
||||
expect(run).to warn_with(/should have a description/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with description" do
|
||||
let(:cask_token) { "with-description" }
|
||||
@ -1045,13 +1018,9 @@ describe Cask::Audit, :cask do
|
||||
expect(run).to pass
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "checking verified" do
|
||||
let(:only) { %w[unnecessary_verified missing_verified no_match required_stanzas] }
|
||||
let(:cask_token) { "foo" }
|
||||
|
||||
context "when the url matches the homepage" do
|
||||
let(:cask_token) { "foo" }
|
||||
let(:cask) do
|
||||
tmp_cask cask_token.to_s, <<~RUBY
|
||||
cask '#{cask_token}' do
|
||||
@ -1070,6 +1039,7 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
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
|
||||
@ -1088,6 +1058,7 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
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
|
||||
@ -1106,6 +1077,7 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
|
||||
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
|
||||
@ -1153,4 +1125,3 @@ describe Cask::Audit, :cask do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user