2023-03-18 16:03:10 -07:00
|
|
|
# typed: true
|
2019-04-19 15:38:03 +09:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2018-08-31 13:16:11 +00:00
|
|
|
require "development_tools"
|
2020-08-09 01:34:07 +02:00
|
|
|
require "cask/exceptions"
|
|
|
|
|
2018-09-06 08:29:14 +02:00
|
|
|
module Cask
|
2020-08-24 23:44:12 +02:00
|
|
|
# Helper module for quarantining files.
|
|
|
|
#
|
|
|
|
# @api private
|
2018-08-31 13:16:11 +00:00
|
|
|
module Quarantine
|
2019-04-19 15:38:03 +09:00
|
|
|
QUARANTINE_ATTRIBUTE = "com.apple.quarantine"
|
2018-08-31 13:16:11 +00:00
|
|
|
|
2018-09-03 19:39:07 +01:00
|
|
|
QUARANTINE_SCRIPT = (HOMEBREW_LIBRARY_PATH/"cask/utils/quarantine.swift").freeze
|
2023-04-03 20:21:37 -05:00
|
|
|
COPY_XATTRS_SCRIPT = (HOMEBREW_LIBRARY_PATH/"cask/utils/copy-xattrs.swift").freeze
|
2018-08-31 13:16:11 +00:00
|
|
|
|
2023-03-18 16:03:10 -07:00
|
|
|
def self.swift
|
2018-08-31 13:16:11 +00:00
|
|
|
@swift ||= DevelopmentTools.locate("swift")
|
|
|
|
end
|
2023-03-18 16:03:10 -07:00
|
|
|
private_class_method :swift
|
2018-08-31 13:16:11 +00:00
|
|
|
|
2023-03-18 16:03:10 -07:00
|
|
|
def self.xattr
|
2018-09-26 20:55:54 +00:00
|
|
|
@xattr ||= DevelopmentTools.locate("xattr")
|
|
|
|
end
|
2023-03-18 16:03:10 -07:00
|
|
|
private_class_method :xattr
|
2018-09-26 20:55:54 +00:00
|
|
|
|
2023-03-18 16:03:10 -07:00
|
|
|
def self.swift_target_args
|
2022-06-02 18:53:46 +01:00
|
|
|
["-target", "#{Hardware::CPU.arch}-apple-macosx#{MacOS.version}"]
|
|
|
|
end
|
2023-03-18 16:03:10 -07:00
|
|
|
private_class_method :swift_target_args
|
2022-06-02 18:53:46 +01:00
|
|
|
|
2020-10-20 12:03:48 +02:00
|
|
|
sig { returns(Symbol) }
|
2023-03-18 16:03:10 -07:00
|
|
|
def self.check_quarantine_support
|
2018-09-04 21:11:29 +00:00
|
|
|
odebug "Checking quarantine support"
|
|
|
|
|
2023-10-25 21:22:06 +08:00
|
|
|
if xattr.nil? || !system_command(xattr, args: ["-h"], print_stderr: false).success?
|
2021-01-24 21:40:41 -05:00
|
|
|
odebug "There's no working version of `xattr` on this system."
|
2018-09-26 20:55:54 +00:00
|
|
|
:xattr_broken
|
|
|
|
elsif swift.nil?
|
2018-09-04 21:11:29 +00:00
|
|
|
odebug "Swift is not available on this system."
|
2018-09-14 15:48:16 +00:00
|
|
|
:no_swift
|
|
|
|
else
|
|
|
|
api_check = system_command(swift,
|
2022-06-02 18:53:46 +01:00
|
|
|
args: [*swift_target_args, QUARANTINE_SCRIPT],
|
2018-09-14 15:48:16 +00:00
|
|
|
print_stderr: false)
|
|
|
|
|
|
|
|
case api_check.exit_status
|
|
|
|
when 2
|
|
|
|
odebug "Quarantine is available."
|
|
|
|
:quarantine_available
|
|
|
|
else
|
|
|
|
odebug "Unknown support status"
|
|
|
|
:unknown
|
|
|
|
end
|
2018-09-04 21:11:29 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-18 16:03:10 -07:00
|
|
|
def self.available?
|
2018-09-04 21:11:29 +00:00
|
|
|
@status ||= check_quarantine_support
|
|
|
|
|
|
|
|
@status == :quarantine_available
|
2018-08-31 13:16:11 +00:00
|
|
|
end
|
|
|
|
|
2023-03-18 16:03:10 -07:00
|
|
|
def self.detect(file)
|
2018-08-31 13:16:11 +00:00
|
|
|
return if file.nil?
|
|
|
|
|
|
|
|
odebug "Verifying Gatekeeper status of #{file}"
|
|
|
|
|
|
|
|
quarantine_status = !status(file).empty?
|
|
|
|
|
|
|
|
odebug "#{file} is #{quarantine_status ? "quarantined" : "not quarantined"}"
|
|
|
|
|
|
|
|
quarantine_status
|
|
|
|
end
|
|
|
|
|
2023-03-18 16:03:10 -07:00
|
|
|
def self.status(file)
|
2018-09-26 20:55:54 +00:00
|
|
|
system_command(xattr,
|
2018-09-04 21:11:29 +00:00
|
|
|
args: ["-p", QUARANTINE_ATTRIBUTE, file],
|
|
|
|
print_stderr: false).stdout.rstrip
|
2018-08-31 13:16:11 +00:00
|
|
|
end
|
|
|
|
|
2023-03-18 16:03:10 -07:00
|
|
|
def self.toggle_no_translocation_bit(attribute)
|
2018-09-26 20:55:54 +00:00
|
|
|
fields = attribute.split(";")
|
2018-09-07 15:37:31 +00:00
|
|
|
|
|
|
|
# Fields: status, epoch, download agent, event ID
|
|
|
|
# Let's toggle the app translocation bit, bit 8
|
2018-10-03 21:03:22 +00:00
|
|
|
# http://www.openradar.me/radar?id=5022734169931776
|
2018-09-07 15:37:31 +00:00
|
|
|
|
|
|
|
fields[0] = (fields[0].to_i(16) | 0x0100).to_s(16).rjust(4, "0")
|
|
|
|
|
|
|
|
fields.join(";")
|
|
|
|
end
|
|
|
|
|
2023-03-18 16:03:10 -07:00
|
|
|
def self.release!(download_path: nil)
|
2018-09-08 14:00:44 +00:00
|
|
|
return unless detect(download_path)
|
2018-08-31 13:16:11 +00:00
|
|
|
|
2018-09-08 14:00:44 +00:00
|
|
|
odebug "Releasing #{download_path} from quarantine"
|
2018-08-31 13:16:11 +00:00
|
|
|
|
2018-09-26 20:55:54 +00:00
|
|
|
quarantiner = system_command(xattr,
|
2018-11-02 17:18:07 +00:00
|
|
|
args: [
|
2018-09-14 15:48:16 +00:00
|
|
|
"-d",
|
|
|
|
QUARANTINE_ATTRIBUTE,
|
|
|
|
download_path,
|
|
|
|
],
|
|
|
|
print_stderr: false)
|
2018-08-31 13:16:11 +00:00
|
|
|
|
2018-09-08 14:00:44 +00:00
|
|
|
return if quarantiner.success?
|
2018-08-31 13:16:11 +00:00
|
|
|
|
2018-09-08 14:00:44 +00:00
|
|
|
raise CaskQuarantineReleaseError.new(download_path, quarantiner.stderr)
|
|
|
|
end
|
2018-09-07 15:37:31 +00:00
|
|
|
|
2023-03-18 16:03:10 -07:00
|
|
|
def self.cask!(cask: nil, download_path: nil, action: true)
|
2018-09-08 14:00:44 +00:00
|
|
|
return if cask.nil? || download_path.nil?
|
2018-09-07 15:37:31 +00:00
|
|
|
|
2018-09-08 14:00:44 +00:00
|
|
|
return if detect(download_path)
|
2018-09-07 15:37:31 +00:00
|
|
|
|
2018-09-08 14:00:44 +00:00
|
|
|
odebug "Quarantining #{download_path}"
|
2018-09-07 15:37:31 +00:00
|
|
|
|
2018-09-08 14:00:44 +00:00
|
|
|
quarantiner = system_command(swift,
|
2018-11-02 17:18:07 +00:00
|
|
|
args: [
|
2022-06-02 18:53:46 +01:00
|
|
|
*swift_target_args,
|
2018-09-14 15:48:16 +00:00
|
|
|
QUARANTINE_SCRIPT,
|
|
|
|
download_path,
|
|
|
|
cask.url.to_s,
|
|
|
|
cask.homepage.to_s,
|
|
|
|
],
|
|
|
|
print_stderr: false)
|
2018-09-07 15:37:31 +00:00
|
|
|
|
2018-09-08 14:00:44 +00:00
|
|
|
return if quarantiner.success?
|
|
|
|
|
|
|
|
case quarantiner.exit_status
|
|
|
|
when 2
|
|
|
|
raise CaskQuarantineError.new(download_path, "Insufficient parameters")
|
|
|
|
else
|
|
|
|
raise CaskQuarantineError.new(download_path, quarantiner.stderr)
|
2018-08-31 13:16:11 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-18 16:03:10 -07:00
|
|
|
def self.propagate(from: nil, to: nil)
|
2018-08-31 13:16:11 +00:00
|
|
|
return if from.nil? || to.nil?
|
|
|
|
|
|
|
|
raise CaskError, "#{from} was not quarantined properly." unless detect(from)
|
|
|
|
|
|
|
|
odebug "Propagating quarantine from #{from} to #{to}"
|
|
|
|
|
2018-09-08 20:20:25 +00:00
|
|
|
quarantine_status = toggle_no_translocation_bit(status(from))
|
2018-09-04 21:11:29 +00:00
|
|
|
|
2019-10-15 18:32:37 +02:00
|
|
|
resolved_paths = Pathname.glob(to/"**/*", File::FNM_DOTMATCH).reject(&:symlink?)
|
2018-09-04 21:11:29 +00:00
|
|
|
|
2018-10-08 01:39:52 +00:00
|
|
|
system_command!("/usr/bin/xargs",
|
2018-11-02 17:18:07 +00:00
|
|
|
args: [
|
2018-10-08 01:39:52 +00:00
|
|
|
"-0",
|
|
|
|
"--",
|
|
|
|
"/bin/chmod",
|
|
|
|
"-h",
|
|
|
|
"u+w",
|
|
|
|
],
|
2018-10-08 18:23:21 +00:00
|
|
|
input: resolved_paths.join("\0"))
|
2018-10-08 01:39:52 +00:00
|
|
|
|
2018-09-04 21:11:29 +00:00
|
|
|
quarantiner = system_command("/usr/bin/xargs",
|
2018-11-02 17:18:07 +00:00
|
|
|
args: [
|
2018-09-04 21:11:29 +00:00
|
|
|
"-0",
|
|
|
|
"--",
|
2018-09-26 20:55:54 +00:00
|
|
|
xattr,
|
2018-09-04 21:11:29 +00:00
|
|
|
"-w",
|
|
|
|
QUARANTINE_ATTRIBUTE,
|
|
|
|
quarantine_status,
|
|
|
|
],
|
2018-11-02 17:18:07 +00:00
|
|
|
input: resolved_paths.join("\0"),
|
2018-09-04 21:11:29 +00:00
|
|
|
print_stderr: false)
|
2018-08-31 13:16:11 +00:00
|
|
|
|
|
|
|
return if quarantiner.success?
|
|
|
|
|
|
|
|
raise CaskQuarantinePropagationError.new(to, quarantiner.stderr)
|
|
|
|
end
|
2023-04-03 20:21:37 -05:00
|
|
|
|
2023-09-06 10:03:45 -04:00
|
|
|
sig { params(from: Pathname, to: Pathname, command: T.class_of(SystemCommand)).void }
|
|
|
|
def self.copy_xattrs(from, to, command:)
|
2023-04-03 20:21:37 -05:00
|
|
|
odebug "Copying xattrs from #{from} to #{to}"
|
|
|
|
|
2023-09-06 10:03:45 -04:00
|
|
|
command.run!(
|
2023-05-03 11:29:01 -05:00
|
|
|
swift,
|
|
|
|
args: [
|
|
|
|
*swift_target_args,
|
|
|
|
COPY_XATTRS_SCRIPT,
|
|
|
|
from,
|
|
|
|
to,
|
|
|
|
],
|
2023-05-24 23:35:21 +02:00
|
|
|
sudo: !to.writable?,
|
2023-05-03 11:29:01 -05:00
|
|
|
)
|
2023-04-03 20:21:37 -05:00
|
|
|
end
|
2023-05-24 21:53:00 -05:00
|
|
|
|
|
|
|
# Ensures that Homebrew has permission to update apps on macOS Ventura.
|
|
|
|
# This may be granted either through the App Management toggle or the Full Disk Access toggle.
|
|
|
|
# The system will only show a prompt for App Management, so we ask the user to grant that.
|
2023-05-26 11:13:23 -05:00
|
|
|
sig { params(app: Pathname, command: T.class_of(SystemCommand)).returns(T::Boolean) }
|
|
|
|
def self.app_management_permissions_granted?(app:, command:)
|
|
|
|
return true unless app.directory?
|
2023-05-24 21:53:00 -05:00
|
|
|
|
|
|
|
# To get macOS to prompt the user for permissions, we need to actually attempt to
|
|
|
|
# modify a file in the app.
|
2023-05-29 08:59:35 +01:00
|
|
|
test_file = app/".homebrew-write-test"
|
2023-05-24 21:53:00 -05:00
|
|
|
|
|
|
|
# We can't use app.writable? here because that conflates several access checks,
|
|
|
|
# including both file ownership and whether system permissions are granted.
|
|
|
|
# Here we just want to check whether sudo would be needed.
|
|
|
|
looks_writable_without_sudo = if app.owned?
|
|
|
|
(app.lstat.mode & 0200) != 0
|
|
|
|
elsif app.grpowned?
|
|
|
|
(app.lstat.mode & 0020) != 0
|
|
|
|
else
|
|
|
|
(app.lstat.mode & 0002) != 0
|
|
|
|
end
|
|
|
|
|
|
|
|
if looks_writable_without_sudo
|
|
|
|
begin
|
|
|
|
File.write(test_file, "")
|
|
|
|
test_file.delete
|
2023-05-26 11:13:23 -05:00
|
|
|
return true
|
2023-06-04 18:04:11 +03:00
|
|
|
rescue Errno::EACCES, Errno::EPERM
|
2023-05-24 21:53:00 -05:00
|
|
|
# Using error handler below
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
command.run!(
|
|
|
|
"touch",
|
|
|
|
args: [
|
|
|
|
test_file,
|
|
|
|
],
|
|
|
|
print_stderr: false,
|
|
|
|
sudo: true,
|
|
|
|
)
|
|
|
|
command.run!(
|
|
|
|
"rm",
|
|
|
|
args: [
|
|
|
|
test_file,
|
|
|
|
],
|
|
|
|
print_stderr: false,
|
|
|
|
sudo: true,
|
|
|
|
)
|
2023-05-26 11:13:23 -05:00
|
|
|
return true
|
2023-05-24 21:53:00 -05:00
|
|
|
rescue ErrorDuringExecution => e
|
|
|
|
# We only want to handle "touch" errors here; propagate "sudo" errors up
|
|
|
|
raise e unless e.stderr.include?("touch: #{test_file}: Operation not permitted")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-05-26 11:13:23 -05:00
|
|
|
opoo <<~EOF
|
|
|
|
Your terminal does not have App Management permissions, so Homebrew will delete and reinstall the app.
|
|
|
|
This may result in some configurations (like notification settings or location in the Dock/Launchpad) being lost.
|
2023-07-06 14:10:13 +01:00
|
|
|
To fix this, go to System Settings > Privacy & Security > App Management and add or enable your terminal.
|
2023-05-24 21:53:00 -05:00
|
|
|
EOF
|
2023-05-29 08:59:35 +01:00
|
|
|
|
2023-05-26 11:13:23 -05:00
|
|
|
false
|
2023-05-24 21:53:00 -05:00
|
|
|
end
|
2018-08-31 13:16:11 +00:00
|
|
|
end
|
|
|
|
end
|