diff --git a/Library/Homebrew/cask/installer.rb b/Library/Homebrew/cask/installer.rb index 708242188fa2d..948b24799367d 100644 --- a/Library/Homebrew/cask/installer.rb +++ b/Library/Homebrew/cask/installer.rb @@ -289,9 +289,9 @@ def downloader token: @cask.token, cask_struct: Homebrew::API::Internal.cask_struct(@cask.token), languages: @cask.config.languages, - require_sha: require_sha? && !force?, + require_sha: require_sha?, ) - end) || Download.new(@cask, require_sha: require_sha? && !force?), + end) || Download.new(@cask, require_sha: require_sha?), T.nilable(Download), ) end diff --git a/Library/Homebrew/cli/args.rb b/Library/Homebrew/cli/args.rb index 05aa0e7f7bc87..05001bd46cd06 100644 --- a/Library/Homebrew/cli/args.rb +++ b/Library/Homebrew/cli/args.rb @@ -20,6 +20,7 @@ def initialize @cli_args = T.let(nil, T.nilable(T::Array[String])) @processed_options = T.let([], OptionsType) + @env_options = T.let([], T::Array[String]) @options_only = T.let([], T::Array[String]) @flags_only = T.let([], T::Array[String]) @cask_options = T.let(false, T::Boolean) @@ -59,13 +60,14 @@ def tap(&_blk) @table[:tap] end - sig { params(processed_options: OptionsType).void } - def freeze_processed_options!(processed_options) + sig { params(processed_options: OptionsType, env_options: T::Array[String]).void } + def freeze_processed_options!(processed_options, env_options: []) # Reset cache values reliant on processed_options @cli_args = nil @processed_options += processed_options @processed_options.freeze + @env_options = env_options.freeze @options_only = cli_args.select { it.start_with?("-") }.freeze @flags_only = cli_args.select { it.start_with?("--") }.freeze @@ -80,6 +82,11 @@ def named sig { returns(T::Boolean) } def no_named? = named.empty? + sig { returns(T::Boolean) } + def cask_require_sha? + @table[:require_sha?] == true && (@table[:force?] != true || @env_options.exclude?("require_sha")) + end + sig { returns(T::Array[String]) } def build_from_source_formulae if @table[:build_from_source?] || @table[:HEAD?] || @table[:build_bottle?] diff --git a/Library/Homebrew/cli/parser.rb b/Library/Homebrew/cli/parser.rb index cc754a3fa9166..b0341b99db7ec 100644 --- a/Library/Homebrew/cli/parser.rb +++ b/Library/Homebrew/cli/parser.rb @@ -532,7 +532,10 @@ def parse(argv = ARGV.freeze, ignore_invalid_options: false) [*remaining, "--", *non_options] end @args.freeze_remaining_args!(remaining_args) - @args.freeze_processed_options!(@processed_options) + @args.freeze_processed_options!( + @processed_options, + env_options: @switch_sources.filter_map { |name, source| name if source == :env }, + ) @args.freeze @args_parsed = T.let(true, T.nilable(TrueClass)) diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb index 8d0bbb1642c6f..17f39ba0b31bf 100644 --- a/Library/Homebrew/cmd/install.rb +++ b/Library/Homebrew/cmd/install.rb @@ -402,7 +402,7 @@ def run force: args.force?, quiet: args.quiet?, skip_cask_deps: args.skip_cask_deps?, - require_sha: args.require_sha?, + require_sha: args.cask_require_sha?, upgrade: upgrade_casks.include?(cask), download_queue:, defer_fetch: true, @@ -464,7 +464,7 @@ def run binaries: args.binaries?, force: args.force?, quiet: args.quiet?, - require_sha: args.require_sha?, + require_sha: args.cask_require_sha?, skip_cask_deps: args.skip_cask_deps?, verbose: args.verbose?, ) @@ -481,7 +481,7 @@ def run force: args.force?, dry_run: args.dry_run?, binaries: args.binaries?, - require_sha: args.require_sha?, + require_sha: args.cask_require_sha?, skip_cask_deps: args.skip_cask_deps?, verbose: args.verbose?, quiet: args.quiet?, diff --git a/Library/Homebrew/cmd/reinstall.rb b/Library/Homebrew/cmd/reinstall.rb index 98e68c357a66c..7e58d817c4b3b 100644 --- a/Library/Homebrew/cmd/reinstall.rb +++ b/Library/Homebrew/cmd/reinstall.rb @@ -274,7 +274,7 @@ def run verbose: args.verbose?, force: args.force?, skip_cask_deps: args.skip_cask_deps?, - require_sha: args.require_sha?, + require_sha: args.cask_require_sha?, reinstall: true, zap: args.zap?, download_queue:, @@ -336,7 +336,7 @@ def run binaries: args.binaries?, verbose: args.verbose?, force: args.force?, - require_sha: args.require_sha?, + require_sha: args.cask_require_sha?, skip_cask_deps: args.skip_cask_deps?, zap: args.zap?, skip_prefetch: casks_prefetched, diff --git a/Library/Homebrew/cmd/upgrade.rb b/Library/Homebrew/cmd/upgrade.rb index 31556c91a6aba..daaaf29249bf8 100644 --- a/Library/Homebrew/cmd/upgrade.rb +++ b/Library/Homebrew/cmd/upgrade.rb @@ -847,7 +847,7 @@ def prefetch_outdated_casks!(casks, download_queue:, prefetch_names: nil, verbose: args.verbose?, force: args.force?, skip_cask_deps: args.skip_cask_deps?, - require_sha: args.require_sha?, + require_sha: args.cask_require_sha?, upgrade: true, download_queue:, defer_fetch: true, @@ -912,7 +912,7 @@ def upgrade_outdated_casks!(casks, skip_prefetch: false, show_upgrade_summary: t greedy_auto_updates: args.greedy_auto_updates?, dry_run:, binaries: args.binaries?, - require_sha: args.require_sha?, + require_sha: args.cask_require_sha?, skip_cask_deps: args.skip_cask_deps?, quit: !args.no_quit?, verbose: args.verbose?, diff --git a/Library/Homebrew/test/cask/installer_spec.rb b/Library/Homebrew/test/cask/installer_spec.rb index 1252897e8225c..53a5789564fed 100644 --- a/Library/Homebrew/test/cask/installer_spec.rb +++ b/Library/Homebrew/test/cask/installer_spec.rb @@ -330,12 +330,12 @@ def stub_dmg_extraction end.not_to raise_error end - it "installs fine if sha256 :no_check is used with --require-sha and --force" do + it "rejects sha256 :no_check with explicit --require-sha and --force" do no_checksum = Cask::CaskLoader.load(cask_path("no-checksum")) - described_class.new(no_checksum, require_sha: true, force: true).install - - expect(no_checksum).to be_installed + expect do + described_class.new(no_checksum, require_sha: true, force: true).install + end.to raise_error(/--require-sha/) end it "records caveats without printing them inline" do diff --git a/Library/Homebrew/test/cli/parser_spec.rb b/Library/Homebrew/test/cli/parser_spec.rb index 0debe5994a308..dffb8254df535 100644 --- a/Library/Homebrew/test/cli/parser_spec.rb +++ b/Library/Homebrew/test/cli/parser_spec.rb @@ -6,6 +6,44 @@ RSpec.describe Homebrew::CLI::Parser do before { stub_const("Cmd", Class.new(Homebrew::AbstractCommand)) } + describe "cask checksum policy" do + subject(:parser) do + described_class.new(Cmd) do + switch "--force" + switch "--require-sha", env: :cask_opts_require_sha + end + end + + it "does not require checksums by default" do + ENV.delete("HOMEBREW_CASK_OPTS") + ENV.delete("HOMEBREW_CASK_OPTS_REQUIRE_SHA") + + expect(parser.parse([]).cask_require_sha?).to be false + end + + it "honours an environment checksum requirement" do + ENV["HOMEBREW_CASK_OPTS"] = "--require-sha" + + expect(parser.parse([]).cask_require_sha?).to be true + end + + it "allows force to override an environment-only requirement" do + ENV["HOMEBREW_CASK_OPTS"] = "--require-sha" + + expect(parser.parse(["--force"]).cask_require_sha?).to be false + end + + it "preserves an explicit requirement with force" do + expect(parser.parse(["--require-sha", "--force"]).cask_require_sha?).to be true + end + + it "preserves an explicit requirement when the environment also requires it" do + ENV["HOMEBREW_CASK_OPTS"] = "--require-sha" + + expect(parser.parse(["--force", "--require-sha"]).cask_require_sha?).to be true + end + end + describe "test switch options" do subject(:parser) do described_class.new(Cmd) do