
Quarantine is available ONLY if the script exits with '2'. It is definitely NOT available if Swift doesn't exist or if it exits with '5' (incompatible SDK). All other cases are from now on treated as unsupported. Also print to standard error only when explictly required (via an exception).
152 lines
4.4 KiB
Ruby
152 lines
4.4 KiB
Ruby
require "development_tools"
|
|
module Cask
|
|
module Quarantine
|
|
module_function
|
|
|
|
QUARANTINE_ATTRIBUTE = "com.apple.quarantine".freeze
|
|
|
|
QUARANTINE_SCRIPT = (HOMEBREW_LIBRARY_PATH/"cask/utils/quarantine.swift").freeze
|
|
|
|
# @private
|
|
def swift
|
|
@swift ||= DevelopmentTools.locate("swift")
|
|
end
|
|
|
|
def check_quarantine_support
|
|
odebug "Checking quarantine support"
|
|
|
|
if swift.nil?
|
|
odebug "Swift is not available on this system."
|
|
:no_swift
|
|
else
|
|
api_check = system_command(swift,
|
|
args: [QUARANTINE_SCRIPT],
|
|
print_stderr: false)
|
|
|
|
case api_check.exit_status
|
|
when 5
|
|
odebug "This feature requires the macOS 10.10 SDK or higher."
|
|
:no_quarantine
|
|
when 2
|
|
odebug "Quarantine is available."
|
|
:quarantine_available
|
|
else
|
|
odebug "Unknown support status"
|
|
:unknown
|
|
end
|
|
end
|
|
end
|
|
|
|
def available?
|
|
@status ||= check_quarantine_support
|
|
|
|
@status == :quarantine_available
|
|
end
|
|
|
|
def detect(file)
|
|
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
|
|
|
|
def status(file)
|
|
system_command("/usr/bin/xattr",
|
|
args: ["-p", QUARANTINE_ATTRIBUTE, file],
|
|
print_stderr: false).stdout.rstrip
|
|
end
|
|
|
|
def toggle_no_translocation_bit(xattr)
|
|
fields = xattr.split(";")
|
|
|
|
# Fields: status, epoch, download agent, event ID
|
|
# Let's toggle the app translocation bit, bit 8
|
|
# http://openradar.me/radar?id=5022734169931776
|
|
|
|
fields[0] = (fields[0].to_i(16) | 0x0100).to_s(16).rjust(4, "0")
|
|
|
|
fields.join(";")
|
|
end
|
|
|
|
def release!(download_path: nil)
|
|
return unless detect(download_path)
|
|
|
|
odebug "Releasing #{download_path} from quarantine"
|
|
|
|
quarantiner = system_command("/usr/bin/xattr",
|
|
args: [
|
|
"-d",
|
|
QUARANTINE_ATTRIBUTE,
|
|
download_path,
|
|
],
|
|
print_stderr: false)
|
|
|
|
return if quarantiner.success?
|
|
|
|
raise CaskQuarantineReleaseError.new(download_path, quarantiner.stderr)
|
|
end
|
|
|
|
def cask!(cask: nil, download_path: nil, action: true)
|
|
return if cask.nil? || download_path.nil?
|
|
|
|
return if detect(download_path)
|
|
|
|
odebug "Quarantining #{download_path}"
|
|
|
|
quarantiner = system_command(swift,
|
|
args: [
|
|
QUARANTINE_SCRIPT,
|
|
download_path,
|
|
cask.url.to_s,
|
|
cask.homepage.to_s,
|
|
],
|
|
print_stderr: false)
|
|
|
|
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)
|
|
end
|
|
end
|
|
|
|
def propagate(from: nil, to: nil)
|
|
return if from.nil? || to.nil?
|
|
|
|
raise CaskError, "#{from} was not quarantined properly." unless detect(from)
|
|
|
|
odebug "Propagating quarantine from #{from} to #{to}"
|
|
|
|
quarantine_status = toggle_no_translocation_bit(status(from))
|
|
|
|
resolved_paths = Pathname.glob(to/"**/*", File::FNM_DOTMATCH)
|
|
|
|
system_command!("/bin/chmod", args: ["-R", "u+w", to])
|
|
|
|
quarantiner = system_command("/usr/bin/xargs",
|
|
args: [
|
|
"-0",
|
|
"--",
|
|
"/usr/bin/xattr",
|
|
"-w",
|
|
"-s",
|
|
QUARANTINE_ATTRIBUTE,
|
|
quarantine_status,
|
|
],
|
|
input: resolved_paths.join("\0"),
|
|
print_stderr: false)
|
|
|
|
return if quarantiner.success?
|
|
|
|
raise CaskQuarantinePropagationError.new(to, quarantiner.stderr)
|
|
end
|
|
end
|
|
end
|