diff --git a/Library/Homebrew/cask/audit.rb b/Library/Homebrew/cask/audit.rb index 7c0a8a6ace..74f255020d 100644 --- a/Library/Homebrew/cask/audit.rb +++ b/Library/Homebrew/cask/audit.rb @@ -6,6 +6,7 @@ require "cask/download" require "digest" require "livecheck/livecheck" require "source_location" +require "system_command" require "utils/curl" require "utils/git" require "utils/shared_audits" @@ -474,21 +475,8 @@ module Cask return if !signing? || download.blank? || cask.url.blank? odebug "Auditing signing" - artifacts = cask.artifacts.select do |k| - k.is_a?(Artifact::Pkg) || k.is_a?(Artifact::App) || k.is_a?(Artifact::Binary) - end - - return if artifacts.empty? - - downloaded_path = download.fetch - primary_container = UnpackStrategy.detect(downloaded_path, type: @cask.container&.type, merge_xattrs: true) - - return if primary_container.nil? - - Dir.mktmpdir do |tmpdir| - tmpdir = Pathname(tmpdir) - primary_container.extract_nestedly(to: tmpdir, basename: downloaded_path.basename, verbose: false) + extract_artifacts do |artifacts, tmpdir| artifacts.each do |artifact| artifact_path = artifact.is_a?(Artifact::Pkg) ? artifact.path : artifact.source path = tmpdir/artifact_path.relative_path_from(cask.staged_path) @@ -509,6 +497,38 @@ module Cask end end + sig { void } + def extract_artifacts + return unless online? + + artifacts = cask.artifacts.select do |artifact| + artifact.is_a?(Artifact::Pkg) || artifact.is_a?(Artifact::App) || artifact.is_a?(Artifact::Binary) + end + + if @artifacts_extracted && @tmpdir + yield artifacts, @tmpdir if block_given? + return + end + + return if artifacts.empty? + + @tmpdir ||= Pathname(Dir.mktmpdir) + + ohai "Downloading and extracting artifacts" + + downloaded_path = download.fetch + + primary_container = UnpackStrategy.detect(downloaded_path, type: @cask.container&.type, merge_xattrs: true) + return if primary_container.nil? + + # Extract the container to the temporary directory. + primary_container.extract_nestedly(to: @tmpdir, basename: downloaded_path.basename, verbose: false) + @artifacts_extracted = true # Set the flag to indicate that extraction has occurred. + + # Yield the artifacts and temp directory to the block if provided. + yield artifacts, @tmpdir if block_given? + end + sig { returns(T.any(NilClass, T::Boolean, Symbol)) } def audit_livecheck_version return unless online? @@ -540,7 +560,39 @@ module Cask false end - def audit_livecheck_min_os + sig { void } + def audit_min_os + return unless online? + return unless strict? + + odebug "Auditing minimum OS version" + + plist_min_os = cask_plist_min_os + sparkle_min_os = livecheck_min_os + + debug_messages = [] + debug_messages << "Plist #{plist_min_os}" if plist_min_os + debug_messages << "Sparkle #{sparkle_min_os}" if sparkle_min_os + odebug "Minimum OS version: #{debug_messages.join(" | ")}" unless debug_messages.empty? + min_os = [sparkle_min_os, plist_min_os].compact.max + + return if min_os.nil? || min_os <= HOMEBREW_MACOS_OLDEST_ALLOWED + + cask_min_os = cask.depends_on.macos&.version + return if cask_min_os == min_os + + min_os_symbol = if cask_min_os.present? + cask_min_os.to_sym.inspect + else + "no minimum OS version" + end + add_error "Upstream defined #{min_os.to_sym.inspect} as the minimum OS version " \ + "and the cask defined #{min_os_symbol}", + strict_only: true + end + + sig { returns(T.nilable(MacOSVersion)) } + def livecheck_min_os return unless online? return unless cask.livecheckable? return if cask.livecheck.strategy != :sparkle @@ -566,24 +618,37 @@ module Cask return if min_os.blank? begin - min_os_string = MacOSVersion.new(min_os).strip_patch + MacOSVersion.new(min_os).strip_patch rescue MacOSVersion::Error - return + nil + end + end + + sig { returns(T.nilable(MacOSVersion)) } + def cask_plist_min_os + return unless online? + + plist_min_os = T.let(nil, T.untyped) + @staged_path ||= cask.staged_path + + extract_artifacts do |artifacts, tmpdir| + artifacts.each do |artifact| + artifact_path = artifact.is_a?(Artifact::Pkg) ? artifact.path : artifact.source + path = tmpdir/artifact_path.relative_path_from(cask.staged_path) + plist_path = "#{path}/Contents/Info.plist" + next unless File.exist?(plist_path) + + plist = system_command!("plutil", args: ["-convert", "xml1", "-o", "-", plist_path]).plist + plist_min_os = plist["LSMinimumSystemVersion"].presence + break if plist_min_os + end end - return if min_os_string <= HOMEBREW_MACOS_OLDEST_ALLOWED - - cask_min_os = cask.depends_on.macos&.version - - return if cask_min_os == min_os_string - - min_os_symbol = if cask_min_os.present? - cask_min_os.to_sym.inspect - else - "no minimum OS version" + begin + MacOSVersion.new(plist_min_os).strip_patch + rescue MacOSVersion::Error + nil end - add_error "Upstream defined #{min_os_string.to_sym.inspect} as the minimum OS version " \ - "and the cask defined #{min_os_symbol}" end sig { void }