diff --git a/Library/Homebrew/cask/audit.rb b/Library/Homebrew/cask/audit.rb index 12c94bf94f..9929e14008 100644 --- a/Library/Homebrew/cask/audit.rb +++ b/Library/Homebrew/cask/audit.rb @@ -21,7 +21,11 @@ module Cask include ::Utils::Curl extend Attrable - attr_reader :cask, :download + sig { returns(Cask) } + attr_reader :cask + + sig { returns(T.nilable(Download)) } + attr_reader :download attr_predicate :new_cask?, :strict?, :signing?, :online?, :token_conflicts? @@ -293,22 +297,22 @@ module Cask def audit_hosting_with_livecheck(livecheck_result: audit_livecheck_version) return if cask.deprecated? || cask.disabled? return if cask.version&.latest? - return unless cask.url + return if (url = cask.url).nil? return if block_url_offline? return if cask.livecheckable? return if livecheck_result == :auto_detected add_livecheck = "please add a livecheck. See #{Formatter.url(LIVECHECK_REFERENCE_URL)}" - case cask.url.to_s + case url.to_s when %r{sourceforge.net/(\S+)} return unless online? - add_error "Download is hosted on SourceForge, #{add_livecheck}", location: cask.url.location + add_error "Download is hosted on SourceForge, #{add_livecheck}", location: url.location when %r{dl.devmate.com/(\S+)} - add_error "Download is hosted on DevMate, #{add_livecheck}", location: cask.url.location + add_error "Download is hosted on DevMate, #{add_livecheck}", location: url.location when %r{rink.hockeyapp.net/(\S+)} - add_error "Download is hosted on HockeyApp, #{add_livecheck}", location: cask.url.location + add_error "Download is hosted on HockeyApp, #{add_livecheck}", location: url.location end end @@ -316,22 +320,22 @@ module Cask sig { void } def audit_download_url_format - return unless cask.url + return if (url = cask.url).nil? return if block_url_offline? odebug "Auditing URL format" return unless bad_sourceforge_url? add_error "SourceForge URL format incorrect. See #{Formatter.url(SOURCEFORGE_OSDN_REFERENCE_URL)}", - location: cask.url.location + location: url.location end def audit_download_url_is_osdn - return unless cask.url + return if (url = cask.url).nil? return if block_url_offline? return unless bad_osdn_url? - add_error "OSDN download urls are disabled.", location: cask.url.location, strict_only: true + add_error "OSDN download urls are disabled.", location: url.location, strict_only: true end VERIFIED_URL_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#when-url-and-homepage-domains-differ-add-verified" @@ -364,15 +368,15 @@ module Cask sig { void } def audit_no_match - return unless cask.url + return if (url = cask.url).nil? return if block_url_offline? return unless verified_present? return if verified_matches_url? add_error "Verified URL #{Formatter.url(url_from_verified)} does not match URL " \ - "#{Formatter.url(strip_url_scheme(cask.url.to_s))}. " \ + "#{Formatter.url(strip_url_scheme(url.to_s))}. " \ "See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}", - location: cask.url.location + location: url.location end sig { void } @@ -446,31 +450,31 @@ module Cask sig { void } def audit_download - return if download.blank? || cask.url.blank? + return if (download = self.download).blank? || (url = cask.url).nil? begin download.fetch rescue => e - add_error "download not possible: #{e}", location: cask.url.location + add_error "download not possible: #{e}", location: url.location end end sig { void } def audit_livecheck_unneeded_long_version - return if cask.version.nil? || cask.url.nil? + return if cask.version.nil? || (url = cask.url).nil? return if cask.livecheck.strategy != :sparkle return unless cask.version.csv.second return if cask.url.to_s.include? cask.version.csv.second return if cask.version.csv.third.present? && cask.url.to_s.include?(cask.version.csv.third) add_error "Download does not require additional version components. Use `&:short_version` in the livecheck", - location: cask.url.location, + location: url.location, strict_only: true end sig { void } def audit_signing - return if !signing? || download.blank? || cask.url.blank? + return if !signing? || download.blank? || (url = cask.url).nil? odebug "Auditing signing" @@ -492,12 +496,12 @@ module Cask when Artifact::Binary system_command("codesign", args: ["--verify", path], print_stderr: false) else - add_error "Unknown artifact type: #{artifact.class}", location: cask.url.location + add_error "Unknown artifact type: #{artifact.class}", location: url.location end next if result.success? - add_error <<~EOS, location: cask.url.location, strict_only: true + add_error <<~EOS, location: url.location, strict_only: true Signature verification failed: #{result.merged_output} macOS on ARM requires software to be signed. @@ -510,6 +514,7 @@ module Cask sig { void } def extract_artifacts return unless online? + return if (download = self.download).nil? artifacts = cask.artifacts.select do |artifact| artifact.is_a?(Artifact::Pkg) || artifact.is_a?(Artifact::App) || artifact.is_a?(Artifact::Binary) @@ -554,6 +559,7 @@ module Cask sig { void } def audit_rosetta + return if (url = cask.url).nil? return unless online? return if Homebrew::SimulateSystem.current_arch != :arm @@ -573,7 +579,7 @@ module Cask files = Dir[path/"Contents/MacOS/*"].select do |f| File.executable?(f) && !File.directory?(f) && !f.end_with?(".dylib") end - add_error "No binaries in App: #{artifact.source}", location: cask.url.location if files.empty? + add_error "No binaries in App: #{artifact.source}", location: url.location if files.empty? main_binary = get_plist_main_binary(path) main_binary ||= files.first @@ -583,7 +589,7 @@ module Cask binary_path = path.to_s.gsub(cask.appdir, tmpdir) system_command("lipo", args: ["-archs", binary_path], print_stderr: true) else - add_error "Unknown artifact type: #{artifact.class}", location: cask.url.location + add_error "Unknown artifact type: #{artifact.class}", location: url.location end # binary stanza can contain shell scripts, so we just continue if lipo fails. @@ -593,7 +599,7 @@ module Cask unless /arm64|x86_64/.match?(result.merged_output) add_error "Artifacts architecture is no longer supported by macOS!", - location: cask.url.location + location: url.location next end @@ -602,10 +608,10 @@ module Cask if supports_arm && mentions_rosetta add_error "Artifacts does not require Rosetta 2 but the caveats say otherwise!", - location: cask.url.location + location: url.location elsif !supports_arm && !mentions_rosetta add_error "Artifacts require Rosetta 2 but this is not indicated by the caveats!", - location: cask.url.location + location: url.location end end end @@ -660,7 +666,8 @@ module Cask return if min_os.nil? || min_os <= HOMEBREW_MACOS_OLDEST_ALLOWED - cask_min_os = [cask.on_system_block_min_os, cask.depends_on.macos&.minimum_version].compact.max + on_system_block_min_os = cask.on_system_block_min_os + cask_min_os = [on_system_block_min_os, cask.depends_on.macos&.minimum_version].compact.max odebug "Declared minimum OS version: #{cask_min_os&.to_sym}" return if cask_min_os&.to_sym == min_os.to_sym return if cask.on_system_blocks_exist? && @@ -669,8 +676,8 @@ module Cask cask_min_os < MacOSVersion.new("11") min_os_definition = if cask_min_os.present? - if cask.on_system_block_min_os.present? && - cask.on_system_block_min_os > cask.depends_on.macos&.minimum_version + if on_system_block_min_os.present? && + on_system_block_min_os > cask.depends_on.macos&.minimum_version "a block with a minimum OS version of #{cask_min_os.to_sym.inspect}" else cask_min_os.to_sym.inspect @@ -761,33 +768,38 @@ module Cask sig { void } def audit_github_prerelease_version + return if (url = cask.url).nil? + odebug "Auditing GitHub prerelease" user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if online? return if user.nil? || repo.nil? - tag = SharedAudits.github_tag_from_url(cask.url) + tag = SharedAudits.github_tag_from_url(url.to_s) tag ||= cask.version error = SharedAudits.github_release(user, repo, tag, cask:) - add_error error, location: cask.url.location if error + add_error error, location: url.location if error end sig { void } def audit_gitlab_prerelease_version + return if (url = cask.url).nil? + user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if online? return if user.nil? || repo.nil? odebug "Auditing GitLab prerelease" - tag = SharedAudits.gitlab_tag_from_url(cask.url) + tag = SharedAudits.gitlab_tag_from_url(url.to_s) tag ||= cask.version error = SharedAudits.gitlab_release(user, repo, tag, cask:) - add_error error, location: cask.url.location if error + add_error error, location: url.location if error end sig { void } def audit_github_repository_archived # Deprecated/disabled casks may have an archived repository. return if cask.deprecated? || cask.disabled? + return if (url = cask.url).nil? user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if online? return if user.nil? || repo.nil? @@ -795,13 +807,14 @@ module Cask metadata = SharedAudits.github_repo_data(user, repo) return if metadata.nil? - add_error "GitHub repo is archived", location: cask.url.location if metadata["archived"] + add_error "GitHub repo is archived", location: url.location if metadata["archived"] end sig { void } def audit_gitlab_repository_archived # Deprecated/disabled casks may have an archived repository. return if cask.deprecated? || cask.disabled? + return if (url = cask.url).nil? user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if online? return if user.nil? || repo.nil? @@ -811,12 +824,13 @@ module Cask metadata = SharedAudits.gitlab_repo_data(user, repo) return if metadata.nil? - add_error "GitLab repo is archived", location: cask.url.location if metadata["archived"] + add_error "GitLab repo is archived", location: url.location if metadata["archived"] end sig { void } def audit_github_repository return unless new_cask? + return if (url = cask.url).nil? user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) return if user.nil? || repo.nil? @@ -824,12 +838,13 @@ module Cask odebug "Auditing GitHub repo" error = SharedAudits.github(user, repo) - add_error error, location: cask.url.location if error + add_error error, location: url.location if error end sig { void } def audit_gitlab_repository return unless new_cask? + return if (url = cask.url).nil? user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) return if user.nil? || repo.nil? @@ -837,12 +852,13 @@ module Cask odebug "Auditing GitLab repo" error = SharedAudits.gitlab(user, repo) - add_error error, location: cask.url.location if error + add_error error, location: url.location if error end sig { void } def audit_bitbucket_repository return unless new_cask? + return if (url = cask.url).nil? user, repo = get_repo_data(%r{https?://bitbucket\.org/([^/]+)/([^/]+)/?.*}) return if user.nil? || repo.nil? @@ -850,7 +866,7 @@ module Cask odebug "Auditing Bitbucket repo" error = SharedAudits.bitbucket(user, repo) - add_error error, location: cask.url.location if error + add_error error, location: url.location if error end sig { void } @@ -899,9 +915,9 @@ module Cask validate_url_for_https_availability( url, "binary URL", - location: cask.url.location, - user_agents: [cask.url.user_agent], - referer: cask.url&.referer + location: url.location, + user_agents: [url.user_agent], + referer: url.referer ) end @@ -1025,7 +1041,7 @@ module Cask # sig { returns(String) } def url_from_verified - strip_url_scheme(cask.url.verified) + strip_url_scheme(T.must(cask.url).verified) end sig { returns(T::Boolean) } @@ -1039,7 +1055,7 @@ module Cask sig { returns(T::Boolean) } def verified_present? - cask.url.verified.present? + cask.url&.verified.present? end sig { returns(T::Boolean) } @@ -1051,7 +1067,7 @@ module Cask def block_url_offline? return false if online? - cask.url.from_block? + !!cask.url&.from_block? end sig { returns(Tap) } diff --git a/Library/Homebrew/cask/cask.rb b/Library/Homebrew/cask/cask.rb index 37c0c4ad07..475e0478fd 100644 --- a/Library/Homebrew/cask/cask.rb +++ b/Library/Homebrew/cask/cask.rb @@ -230,6 +230,8 @@ module Cask end def checksumable? + return false if (url = self.url).nil? + DownloadStrategyDetector.detect(url.to_s, url.using) <= AbstractFileDownloadStrategy end diff --git a/Library/Homebrew/cask/cask.rbi b/Library/Homebrew/cask/cask.rbi index 4880030ae6..9ffa1b2428 100644 --- a/Library/Homebrew/cask/cask.rbi +++ b/Library/Homebrew/cask/cask.rbi @@ -46,10 +46,14 @@ module Cask def on_system_blocks_exist?; end + sig { returns(T.nilable(MacOSVersion)) } + def on_system_block_min_os; end + def sha256; end def staged_path; end + sig { returns(T.nilable(::Cask::URL)) } def url; end def version; end diff --git a/Library/Homebrew/cask/url.rbi b/Library/Homebrew/cask/url.rbi index 7675ab841e..3c56476e6d 100644 --- a/Library/Homebrew/cask/url.rbi +++ b/Library/Homebrew/cask/url.rbi @@ -3,5 +3,22 @@ module Cask class URL include Kernel + + # TODO: Generate this + + sig { returns(T.any(T::Class[T.anything], Symbol, NilClass)) } + def using; end + + sig { returns(T.nilable(T.any(URI::Generic, String))) } + def referer; end + + sig { returns(T::Hash[Symbol, T.untyped]) } + def specs; end + + sig { returns(T.nilable(T.any(Symbol, String))) } + def user_agent; end + + sig { returns(T.nilable(String)) } + def verified; end end end diff --git a/Library/Homebrew/livecheck/livecheck.rb b/Library/Homebrew/livecheck/livecheck.rb index dcb45e7b5a..61bca36115 100644 --- a/Library/Homebrew/livecheck/livecheck.rb +++ b/Library/Homebrew/livecheck/livecheck.rb @@ -635,7 +635,7 @@ module Homebrew homebrew_curl_root_domains << domain if domain.present? end when Cask::Cask - return false if formula_or_cask.url.using != :homebrew_curl + return false if formula_or_cask.url&.using != :homebrew_curl domain = Addressable::URI.parse(formula_or_cask.url.to_s)&.domain homebrew_curl_root_domains << domain if domain.present?