Merge pull request #4656 from amyspark/com-apple-quarantine
Enable quarantining of Homebrew-Cask's downloads
This commit is contained in:
commit
47d3bbee1c
@ -65,11 +65,8 @@ module Hbc
|
||||
ohai "Backing #{self.class.english_name} '#{target.basename}' up to '#{source}'."
|
||||
source.dirname.mkpath
|
||||
|
||||
if target.parent.writable?
|
||||
FileUtils.cp_r(target, source)
|
||||
else
|
||||
command.run!("/bin/cp", args: ["-r", target, source], sudo: true)
|
||||
end
|
||||
# We need to preserve extended attributes between copies.
|
||||
command.run!("/bin/cp", args: ["-pR", target, source], sudo: !target.parent.writable?)
|
||||
|
||||
delete(target, force: force, command: command, **options)
|
||||
end
|
||||
|
||||
@ -2,15 +2,16 @@ require "hbc/download"
|
||||
|
||||
module Hbc
|
||||
class Auditor
|
||||
def self.audit(cask, audit_download: false, check_token_conflicts: false, commit_range: nil)
|
||||
new(cask, audit_download, check_token_conflicts, commit_range).audit
|
||||
def self.audit(cask, audit_download: false, check_token_conflicts: false, quarantine: true, commit_range: nil)
|
||||
new(cask, audit_download, check_token_conflicts, quarantine, commit_range).audit
|
||||
end
|
||||
|
||||
attr_reader :cask, :commit_range
|
||||
|
||||
def initialize(cask, audit_download, check_token_conflicts, commit_range)
|
||||
def initialize(cask, audit_download, check_token_conflicts, quarantine, commit_range)
|
||||
@cask = cask
|
||||
@audit_download = audit_download
|
||||
@quarantine = quarantine
|
||||
@commit_range = commit_range
|
||||
@check_token_conflicts = check_token_conflicts
|
||||
end
|
||||
@ -19,6 +20,10 @@ module Hbc
|
||||
@audit_download
|
||||
end
|
||||
|
||||
def quarantine?
|
||||
@quarantine
|
||||
end
|
||||
|
||||
def check_token_conflicts?
|
||||
@check_token_conflicts
|
||||
end
|
||||
@ -52,7 +57,7 @@ module Hbc
|
||||
end
|
||||
|
||||
def audit_cask_instance(cask)
|
||||
download = audit_download? && Download.new(cask)
|
||||
download = audit_download? && Download.new(cask, quarantine: quarantine?)
|
||||
audit = Audit.new(cask, download: download,
|
||||
check_token_conflicts: check_token_conflicts?,
|
||||
commit_range: commit_range)
|
||||
|
||||
@ -7,11 +7,12 @@ module Hbc
|
||||
include Options
|
||||
include Homebrew::Search
|
||||
|
||||
option "--[no-]binaries", :binaries, true
|
||||
option "--debug", :debug, false
|
||||
option "--verbose", :verbose, false
|
||||
option "--outdated", :outdated_only, false
|
||||
option "--require-sha", :require_sha, false
|
||||
option "--[no-]binaries", :binaries, true
|
||||
option "--debug", :debug, false
|
||||
option "--verbose", :verbose, false
|
||||
option "--outdated", :outdated_only, false
|
||||
option "--require-sha", :require_sha, false
|
||||
option "--[no-]quarantine", :quarantine, true
|
||||
|
||||
def self.command_name
|
||||
@command_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1_\2').downcase
|
||||
|
||||
@ -18,7 +18,9 @@ module Hbc
|
||||
|
||||
def audit(cask)
|
||||
odebug "Auditing Cask #{cask}"
|
||||
Auditor.audit(cask, audit_download: download?, check_token_conflicts: token_conflicts?)
|
||||
Auditor.audit(cask, audit_download: download?,
|
||||
check_token_conflicts: token_conflicts?,
|
||||
quarantine: quarantine?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -14,7 +14,7 @@ module Hbc
|
||||
casks.each do |cask|
|
||||
Installer.print_caveats(cask)
|
||||
ohai "Downloading external files for Cask #{cask}"
|
||||
downloaded_path = Download.new(cask, force: force?).perform
|
||||
downloaded_path = Download.new(cask, force: force?, quarantine: quarantine?).perform
|
||||
Verify.all(cask, downloaded_path)
|
||||
ohai "Success! Downloaded to -> #{downloaded_path}"
|
||||
end
|
||||
|
||||
@ -17,7 +17,8 @@ module Hbc
|
||||
verbose: verbose?,
|
||||
force: force?,
|
||||
skip_cask_deps: skip_cask_deps?,
|
||||
require_sha: require_sha?).install
|
||||
require_sha: require_sha?,
|
||||
quarantine: quarantine?).install
|
||||
rescue CaskAlreadyInstalledError => e
|
||||
opoo e.message
|
||||
end
|
||||
|
||||
@ -7,7 +7,8 @@ module Hbc
|
||||
verbose: verbose?,
|
||||
force: force?,
|
||||
skip_cask_deps: skip_cask_deps?,
|
||||
require_sha: require_sha?).reinstall
|
||||
require_sha: require_sha?,
|
||||
quarantine: quarantine?).reinstall
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -36,7 +36,11 @@ module Hbc
|
||||
|
||||
old_cask = CaskLoader.load(old_cask.installed_caskfile)
|
||||
|
||||
old_cask_installer = Installer.new(old_cask, binaries: binaries?, verbose: verbose?, force: force?, upgrade: true)
|
||||
old_cask_installer =
|
||||
Installer.new(old_cask, binaries: binaries?,
|
||||
verbose: verbose?,
|
||||
force: force?,
|
||||
upgrade: true)
|
||||
|
||||
new_cask = CaskLoader.load(old_cask.to_s)
|
||||
|
||||
@ -46,7 +50,8 @@ module Hbc
|
||||
force: force?,
|
||||
skip_cask_deps: skip_cask_deps?,
|
||||
require_sha: require_sha?,
|
||||
upgrade: true)
|
||||
upgrade: true,
|
||||
quarantine: quarantine?)
|
||||
|
||||
started_upgrade = false
|
||||
new_artifacts_installed = false
|
||||
|
||||
@ -1,18 +1,21 @@
|
||||
require "fileutils"
|
||||
require "hbc/quarantine"
|
||||
require "hbc/verify"
|
||||
|
||||
module Hbc
|
||||
class Download
|
||||
attr_reader :cask
|
||||
|
||||
def initialize(cask, force: false)
|
||||
def initialize(cask, force: false, quarantine: true)
|
||||
@cask = cask
|
||||
@force = force
|
||||
@quarantine = quarantine
|
||||
end
|
||||
|
||||
def perform
|
||||
clear_cache
|
||||
fetch
|
||||
quarantine
|
||||
downloaded_path
|
||||
end
|
||||
|
||||
@ -38,5 +41,13 @@ module Hbc
|
||||
rescue StandardError => e
|
||||
raise CaskError, "Download failed on Cask '#{cask}' with message: #{e}"
|
||||
end
|
||||
|
||||
def quarantine
|
||||
return unless @quarantine
|
||||
return unless Quarantine.available?
|
||||
return if Quarantine.detect(@downloaded_path)
|
||||
|
||||
Quarantine.cask(cask: @cask, download_path: @downloaded_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -32,13 +32,13 @@ module Hbc
|
||||
|
||||
class CaskUnavailableError < AbstractCaskErrorWithToken
|
||||
def to_s
|
||||
"Cask '#{token}' is unavailable" << (reason.empty? ? "." : ": #{reason}")
|
||||
"Cask '#{token}' is unavailable#{reason.empty? ? "." : ": #{reason}"}"
|
||||
end
|
||||
end
|
||||
|
||||
class CaskUnreadableError < CaskUnavailableError
|
||||
def to_s
|
||||
"Cask '#{token}' is unreadable" << (reason.empty? ? "." : ": #{reason}")
|
||||
"Cask '#{token}' is unreadable#{reason.empty? ? "." : ": #{reason}"}"
|
||||
end
|
||||
end
|
||||
|
||||
@ -73,7 +73,7 @@ module Hbc
|
||||
|
||||
class CaskCyclicDependencyError < AbstractCaskErrorWithToken
|
||||
def to_s
|
||||
"Cask '#{token}' includes cyclic dependencies on other Casks" << (reason.empty? ? "." : ": #{reason}")
|
||||
"Cask '#{token}' includes cyclic dependencies on other Casks#{reason.empty? ? "." : ": #{reason}"}"
|
||||
end
|
||||
end
|
||||
|
||||
@ -91,7 +91,7 @@ module Hbc
|
||||
|
||||
class CaskInvalidError < AbstractCaskErrorWithToken
|
||||
def to_s
|
||||
"Cask '#{token}' definition is invalid" << (reason.empty? ? "." : ": #{reason}")
|
||||
"Cask '#{token}' definition is invalid#{reason.empty? ? "." : ": #{reason}"}"
|
||||
end
|
||||
end
|
||||
|
||||
@ -149,4 +149,39 @@ module Hbc
|
||||
EOS
|
||||
end
|
||||
end
|
||||
|
||||
class CaskQuarantineError < CaskError
|
||||
attr_reader :path, :reason
|
||||
|
||||
def initialize(path, reason)
|
||||
@path = path
|
||||
@reason = reason
|
||||
end
|
||||
|
||||
def to_s
|
||||
s = "Failed to quarantine #{path}."
|
||||
|
||||
unless reason.empty?
|
||||
s << " Here's the reason:\n"
|
||||
s << Formatter.error(reason)
|
||||
s << "\n" unless reason.end_with?("\n")
|
||||
end
|
||||
|
||||
s
|
||||
end
|
||||
end
|
||||
|
||||
class CaskQuarantinePropagationError < CaskQuarantineError
|
||||
def to_s
|
||||
s = "Failed to quarantine one or more files within #{path}."
|
||||
|
||||
unless reason.empty?
|
||||
s << " Here's the reason:\n"
|
||||
s << Formatter.error(reason)
|
||||
s << "\n" unless reason.end_with?("\n")
|
||||
end
|
||||
|
||||
s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -7,6 +7,7 @@ require "hbc/cask_dependencies"
|
||||
require "hbc/download"
|
||||
require "hbc/staged"
|
||||
require "hbc/verify"
|
||||
require "hbc/quarantine"
|
||||
|
||||
require "cgi"
|
||||
|
||||
@ -23,7 +24,10 @@ module Hbc
|
||||
|
||||
PERSISTENT_METADATA_SUBDIRS = ["gpg"].freeze
|
||||
|
||||
def initialize(cask, command: SystemCommand, force: false, skip_cask_deps: false, binaries: true, verbose: false, require_sha: false, upgrade: false, installed_as_dependency: false)
|
||||
def initialize(cask, command: SystemCommand, force: false,
|
||||
skip_cask_deps: false, binaries: true, verbose: false,
|
||||
require_sha: false, upgrade: false,
|
||||
installed_as_dependency: false, quarantine: true)
|
||||
@cask = cask
|
||||
@command = command
|
||||
@force = force
|
||||
@ -34,9 +38,12 @@ module Hbc
|
||||
@reinstall = false
|
||||
@upgrade = upgrade
|
||||
@installed_as_dependency = installed_as_dependency
|
||||
@quarantine = quarantine
|
||||
end
|
||||
|
||||
attr_predicate :binaries?, :force?, :skip_cask_deps?, :require_sha?, :upgrade?, :verbose?, :installed_as_dependency?
|
||||
attr_predicate :binaries?, :force?, :skip_cask_deps?, :require_sha?,
|
||||
:upgrade?, :verbose?, :installed_as_dependency?,
|
||||
:quarantine?
|
||||
|
||||
def self.print_caveats(cask)
|
||||
odebug "Printing caveats"
|
||||
@ -86,6 +93,7 @@ module Hbc
|
||||
uninstall_existing_cask if @reinstall
|
||||
|
||||
oh1 "Installing Cask #{Formatter.identifier(@cask)}"
|
||||
opoo "macOS's Gatekeeper has been disabled for this Cask" unless quarantine?
|
||||
stage
|
||||
install_artifacts
|
||||
enable_accessibility_access
|
||||
@ -137,7 +145,7 @@ module Hbc
|
||||
|
||||
def download
|
||||
odebug "Downloading"
|
||||
@downloaded_path = Download.new(@cask, force: false).perform
|
||||
@downloaded_path = Download.new(@cask, force: false, quarantine: quarantine?).perform
|
||||
odebug "Downloaded to -> #{@downloaded_path}"
|
||||
@downloaded_path
|
||||
end
|
||||
@ -176,6 +184,11 @@ module Hbc
|
||||
else
|
||||
primary_container.extract_nestedly(to: @cask.staged_path, basename: basename, verbose: verbose?)
|
||||
end
|
||||
|
||||
return unless quarantine?
|
||||
return unless Quarantine.available?
|
||||
|
||||
Quarantine.propagate(from: @downloaded_path, to: @cask.staged_path, command: @command)
|
||||
end
|
||||
|
||||
def install_artifacts
|
||||
|
||||
80
Library/Homebrew/cask/lib/hbc/quarantine.rb
Normal file
80
Library/Homebrew/cask/lib/hbc/quarantine.rb
Normal file
@ -0,0 +1,80 @@
|
||||
require "development_tools"
|
||||
module Hbc
|
||||
module Quarantine
|
||||
module_function
|
||||
|
||||
QUARANTINE_ATTRIBUTE = "com.apple.quarantine".freeze
|
||||
|
||||
QUARANTINE_SCRIPT = (HOMEBREW_LIBRARY_PATH/"cask/lib/hbc/utils/quarantine.swift").freeze
|
||||
|
||||
# @private
|
||||
def swift
|
||||
@swift ||= DevelopmentTools.locate("swift")
|
||||
end
|
||||
|
||||
def available?
|
||||
status = !swift.nil?
|
||||
odebug "Quarantine is #{status ? "available" : "not available"}."
|
||||
status
|
||||
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, command: SystemCommand)
|
||||
command.run("/usr/bin/xattr",
|
||||
args: ["-p", QUARANTINE_ATTRIBUTE, file],
|
||||
print_stderr: false).stdout.rstrip
|
||||
end
|
||||
|
||||
def cask(cask: nil, download_path: nil, command: SystemCommand)
|
||||
return if cask.nil? || download_path.nil?
|
||||
|
||||
odebug "Quarantining #{download_path}"
|
||||
|
||||
quarantiner = command.run(swift,
|
||||
args: [
|
||||
QUARANTINE_SCRIPT,
|
||||
download_path,
|
||||
cask.url.to_s,
|
||||
cask.homepage.to_s,
|
||||
])
|
||||
|
||||
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, command: SystemCommand)
|
||||
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 = status(from, command: command)
|
||||
|
||||
quarantiner = command.run("/usr/bin/xattr",
|
||||
args: ["-w", "-r", QUARANTINE_ATTRIBUTE, quarantine_status, to],
|
||||
print_stderr: false)
|
||||
|
||||
return if quarantiner.success?
|
||||
|
||||
raise CaskQuarantinePropagationError.new(to, quarantiner.stderr)
|
||||
end
|
||||
end
|
||||
end
|
||||
42
Library/Homebrew/cask/lib/hbc/utils/quarantine.swift
Normal file
42
Library/Homebrew/cask/lib/hbc/utils/quarantine.swift
Normal file
@ -0,0 +1,42 @@
|
||||
#!/usr/bin/swift
|
||||
|
||||
import Foundation
|
||||
|
||||
struct swifterr: TextOutputStream {
|
||||
public static var stream = swifterr()
|
||||
mutating func write(_ string: String) { fputs(string, stderr) }
|
||||
}
|
||||
|
||||
if (CommandLine.arguments.count < 4) {
|
||||
exit(2)
|
||||
}
|
||||
|
||||
let dataLocationUrl: NSURL = NSURL.init(fileURLWithPath: CommandLine.arguments[1])
|
||||
|
||||
var errorBag: NSError?
|
||||
|
||||
let quarantineProperties: [String: Any] = [
|
||||
kLSQuarantineAgentNameKey as String: "Homebrew-Cask",
|
||||
kLSQuarantineTypeKey as String: kLSQuarantineTypeWebDownload,
|
||||
kLSQuarantineDataURLKey as String: CommandLine.arguments[2],
|
||||
kLSQuarantineOriginURLKey as String: CommandLine.arguments[3]
|
||||
]
|
||||
|
||||
if (dataLocationUrl.checkResourceIsReachableAndReturnError(&errorBag)) {
|
||||
do {
|
||||
try dataLocationUrl.setResourceValue(
|
||||
quarantineProperties as NSDictionary,
|
||||
forKey: URLResourceKey.quarantinePropertiesKey
|
||||
)
|
||||
}
|
||||
catch {
|
||||
print(error.localizedDescription, to: &swifterr.stream)
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
else {
|
||||
print(errorBag!.localizedDescription, to: &swifterr.stream)
|
||||
exit(3)
|
||||
}
|
||||
|
||||
exit(0)
|
||||
@ -19,7 +19,7 @@ names, and other aspects of this manual are still subject to change.
|
||||
|
||||
## FREQUENTLY USED COMMANDS
|
||||
|
||||
* `install` [--force] [--skip-cask-deps] [--require-sha] [--language=<iso-language>[,<iso-language> ... ]] <token> [ <token> ... ]:
|
||||
* `install` [--force] [--skip-cask-deps] [--require-sha] [--no-quarantine] [--language=<iso-language>[,<iso-language> ... ]] <token> [ <token> ... ]:
|
||||
Install Cask identified by <token>.
|
||||
|
||||
* `uninstall` [--force] <token> [ <token> ... ]:
|
||||
@ -56,10 +56,11 @@ names, and other aspects of this manual are still subject to change.
|
||||
* `edit` <token>:
|
||||
Open the given Cask definition file for editing.
|
||||
|
||||
* `fetch` [--force] <token> [ <token> ... ]:
|
||||
* `fetch` [--force] [--no-quarantine] <token> [ <token> ... ]:
|
||||
Download remote application files for the given Cask to the local
|
||||
cache. With `--force`, force re-download even if the files are already
|
||||
cached.
|
||||
cached. `--no-quarantine` will prevent Gatekeeper from
|
||||
enforcing its security restrictions on the Cask.
|
||||
|
||||
* `home` or `homepage` [ <token> ... ]:
|
||||
Display the homepage associated with a given Cask in a browser.
|
||||
@ -69,11 +70,12 @@ names, and other aspects of this manual are still subject to change.
|
||||
* `info` or `abv` <token> [ <token> ... ]:
|
||||
Display information about the given Cask.
|
||||
|
||||
* `install` [--force] [--skip-cask-deps] [--require-sha] <token> [ <token> ... ]:
|
||||
* `install` [--force] [--skip-cask-deps] [--require-sha] [--no-quarantine] <token> [ <token> ... ]:
|
||||
Install the given Cask. With `--force`, re-install even if the Cask
|
||||
appears to be already present. With `--skip-cask-deps`, skip any Cask
|
||||
dependencies. `--require-sha` will abort installation if the Cask does not
|
||||
have a checksum defined.
|
||||
have a checksum defined. `--no-quarantine` will prevent Gatekeeper from
|
||||
enforcing its security restrictions on the Cask.
|
||||
|
||||
<token> is usually the ID of a Cask,
|
||||
but see [OTHER WAYS TO SPECIFY A CASK][] for variations.
|
||||
@ -97,7 +99,7 @@ names, and other aspects of this manual are still subject to change.
|
||||
`--verbose` forces the display of the outdated and latest version.<br>
|
||||
`--quiet` suppresses the display of versions.
|
||||
|
||||
* `reinstall` <token> [ <token> ... ]:
|
||||
* `reinstall` [--no-quarantine] <token> [ <token> ... ]:
|
||||
Reinstall the given Cask.
|
||||
|
||||
* `search` or `-S` [<text> | /<regexp>/]:
|
||||
@ -162,6 +164,10 @@ in a future version.
|
||||
* `--require-sha`:
|
||||
Abort Cask installation if the Cask does not have a checksum defined.
|
||||
|
||||
* `--no-quarantine`:
|
||||
Prevent Gatekeeper from enforcing its security restrictions on the Cask.
|
||||
This will let you run it straightaway.
|
||||
|
||||
* `--verbose`:
|
||||
Give additional feedback during installation.
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ describe Hbc::CLI::Audit, :cask do
|
||||
expect(Hbc::CaskLoader).to receive(:load).with(cask_token).and_return(cask)
|
||||
|
||||
expect(Hbc::Auditor).to receive(:audit)
|
||||
.with(cask, audit_download: false, check_token_conflicts: false)
|
||||
.with(cask, audit_download: false, check_token_conflicts: false, quarantine: true)
|
||||
.and_return(true)
|
||||
|
||||
described_class.run(cask_token)
|
||||
@ -30,7 +30,7 @@ describe Hbc::CLI::Audit, :cask do
|
||||
it "does not download the Cask per default" do
|
||||
allow(Hbc::CaskLoader).to receive(:load).and_return(cask)
|
||||
expect(Hbc::Auditor).to receive(:audit)
|
||||
.with(cask, audit_download: false, check_token_conflicts: false)
|
||||
.with(cask, audit_download: false, check_token_conflicts: false, quarantine: true)
|
||||
.and_return(true)
|
||||
|
||||
described_class.run("casktoken")
|
||||
@ -39,7 +39,7 @@ describe Hbc::CLI::Audit, :cask do
|
||||
it "download a Cask if --download flag is set" do
|
||||
allow(Hbc::CaskLoader).to receive(:load).and_return(cask)
|
||||
expect(Hbc::Auditor).to receive(:audit)
|
||||
.with(cask, audit_download: true, check_token_conflicts: false)
|
||||
.with(cask, audit_download: true, check_token_conflicts: false, quarantine: true)
|
||||
.and_return(true)
|
||||
|
||||
described_class.run("casktoken", "--download")
|
||||
@ -50,7 +50,7 @@ describe Hbc::CLI::Audit, :cask do
|
||||
it "does not check for token conflicts per default" do
|
||||
allow(Hbc::CaskLoader).to receive(:load).and_return(cask)
|
||||
expect(Hbc::Auditor).to receive(:audit)
|
||||
.with(cask, audit_download: false, check_token_conflicts: false)
|
||||
.with(cask, audit_download: false, check_token_conflicts: false, quarantine: true)
|
||||
.and_return(true)
|
||||
|
||||
described_class.run("casktoken")
|
||||
@ -59,7 +59,7 @@ describe Hbc::CLI::Audit, :cask do
|
||||
it "checks for token conflicts if --token-conflicts flag is set" do
|
||||
allow(Hbc::CaskLoader).to receive(:load).and_return(cask)
|
||||
expect(Hbc::Auditor).to receive(:audit)
|
||||
.with(cask, audit_download: false, check_token_conflicts: true)
|
||||
.with(cask, audit_download: false, check_token_conflicts: true, quarantine: true)
|
||||
.and_return(true)
|
||||
|
||||
described_class.run("casktoken", "--token-conflicts")
|
||||
|
||||
@ -51,7 +51,7 @@ describe Hbc::CLI::Info, :cask do
|
||||
Not installed
|
||||
From: https://github.com/Homebrew/homebrew-cask/blob/master/Casks/local-transmission.rb
|
||||
==> Name
|
||||
None
|
||||
Transmission
|
||||
==> Artifacts
|
||||
Transmission.app (App)
|
||||
EOS
|
||||
|
||||
213
Library/Homebrew/test/cask/cli/quarantine_spec.rb
Normal file
213
Library/Homebrew/test/cask/cli/quarantine_spec.rb
Normal file
@ -0,0 +1,213 @@
|
||||
describe Hbc::Quarantine, :cask do
|
||||
matcher :be_quarantined do
|
||||
match do |path|
|
||||
expect(
|
||||
described_class.detect(path),
|
||||
).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe "by default" do
|
||||
it "quarantines a nice fresh Cask" do
|
||||
Hbc::CLI::Install.run("local-transmission")
|
||||
|
||||
expect(
|
||||
Hbc::CaskLoader.load(cask_path("local-transmission")),
|
||||
).to be_installed
|
||||
|
||||
expect(
|
||||
Hbc::Config.global.appdir.join("Transmission.app"),
|
||||
).to be_quarantined
|
||||
end
|
||||
|
||||
it "quarantines Cask fetches" do
|
||||
Hbc::CLI::Fetch.run("local-transmission")
|
||||
local_transmission = Hbc::CaskLoader.load(cask_path("local-transmission"))
|
||||
cached_location = Hbc::Download.new(local_transmission, force: false, quarantine: false).perform
|
||||
|
||||
expect(cached_location).to be_quarantined
|
||||
end
|
||||
|
||||
it "quarantines Cask audits" do
|
||||
Hbc::CLI::Audit.run("local-transmission", "--download")
|
||||
|
||||
local_transmission = Hbc::CaskLoader.load(cask_path("local-transmission"))
|
||||
cached_location = Hbc::Download.new(local_transmission, force: false, quarantine: false).perform
|
||||
|
||||
expect(cached_location).to be_quarantined
|
||||
end
|
||||
|
||||
it "quarantines dmg-based Casks" do
|
||||
Hbc::CLI::Install.run("container-dmg")
|
||||
|
||||
expect(
|
||||
Hbc::CaskLoader.load(cask_path("container-dmg")),
|
||||
).to be_installed
|
||||
|
||||
expect(Hbc::Config.global.appdir.join("container")).to be_quarantined
|
||||
end
|
||||
|
||||
it "quarantines tar-gz-based Casks" do
|
||||
Hbc::CLI::Install.run("container-tar-gz")
|
||||
|
||||
expect(
|
||||
Hbc::CaskLoader.load(cask_path("container-tar-gz")),
|
||||
).to be_installed
|
||||
|
||||
expect(Hbc::Config.global.appdir.join("container")).to be_quarantined
|
||||
end
|
||||
|
||||
it "quarantines xar-based Casks" do
|
||||
Hbc::CLI::Install.run("container-xar")
|
||||
|
||||
expect(
|
||||
Hbc::CaskLoader.load(cask_path("container-xar")),
|
||||
).to be_installed
|
||||
|
||||
expect(Hbc::Config.global.appdir.join("container")).to be_quarantined
|
||||
end
|
||||
|
||||
it "quarantines pure bzip2-based Casks" do
|
||||
Hbc::CLI::Install.run("container-bzip2")
|
||||
|
||||
expect(
|
||||
Hbc::CaskLoader.load(cask_path("container-bzip2")),
|
||||
).to be_installed
|
||||
|
||||
expect(Hbc::Config.global.appdir.join("container")).to be_quarantined
|
||||
end
|
||||
|
||||
it "quarantines pure gzip-based Casks" do
|
||||
Hbc::CLI::Install.run("container-gzip")
|
||||
|
||||
expect(
|
||||
Hbc::CaskLoader.load(cask_path("container-gzip")),
|
||||
).to be_installed
|
||||
|
||||
expect(Hbc::Config.global.appdir.join("container")).to be_quarantined
|
||||
end
|
||||
|
||||
it "quarantines the pkg in naked-pkg-based Casks" do
|
||||
Hbc::CLI::Install.run("container-pkg")
|
||||
|
||||
naked_pkg = Hbc::CaskLoader.load(cask_path("container-pkg"))
|
||||
|
||||
expect(naked_pkg).to be_installed
|
||||
|
||||
expect(
|
||||
Hbc::Caskroom.path.join("container-pkg", naked_pkg.version, "container.pkg"),
|
||||
).to be_quarantined
|
||||
end
|
||||
|
||||
it "quarantines a nested container" do
|
||||
Hbc::CLI::Install.run("nested-app")
|
||||
|
||||
expect(
|
||||
Hbc::CaskLoader.load(cask_path("nested-app")),
|
||||
).to be_installed
|
||||
|
||||
expect(Hbc::Config.global.appdir.join("MyNestedApp.app")).to be_quarantined
|
||||
end
|
||||
end
|
||||
|
||||
describe "when disabled" do
|
||||
it "does not quarantine even a nice, fresh Cask" do
|
||||
Hbc::CLI::Install.run("local-transmission", "--no-quarantine")
|
||||
|
||||
expect(
|
||||
Hbc::CaskLoader.load(cask_path("local-transmission")),
|
||||
).to be_installed
|
||||
|
||||
expect(Hbc::Config.global.appdir.join("Transmission.app")).to_not be_quarantined
|
||||
end
|
||||
|
||||
it "does not quarantine Cask fetches" do
|
||||
Hbc::CLI::Fetch.run("local-transmission", "--no-quarantine")
|
||||
local_transmission = Hbc::CaskLoader.load(cask_path("local-transmission"))
|
||||
cached_location = Hbc::Download.new(local_transmission, force: false, quarantine: false).perform
|
||||
|
||||
expect(cached_location).to_not be_quarantined
|
||||
end
|
||||
|
||||
it "does not quarantine dmg-based Casks" do
|
||||
Hbc::CLI::Install.run("container-dmg", "--no-quarantine")
|
||||
|
||||
expect(
|
||||
Hbc::CaskLoader.load(cask_path("container-dmg")),
|
||||
).to be_installed
|
||||
|
||||
expect(Hbc::Config.global.appdir.join("container")).to_not be_quarantined
|
||||
end
|
||||
|
||||
it "does not quarantine tar-gz-based Casks" do
|
||||
Hbc::CLI::Install.run("container-tar-gz", "--no-quarantine")
|
||||
|
||||
expect(
|
||||
Hbc::CaskLoader.load(cask_path("container-tar-gz")),
|
||||
).to be_installed
|
||||
|
||||
expect(Hbc::Config.global.appdir.join("container")).to_not be_quarantined
|
||||
end
|
||||
|
||||
it "does not quarantine xar-based Casks" do
|
||||
Hbc::CLI::Install.run("container-xar", "--no-quarantine")
|
||||
|
||||
expect(
|
||||
Hbc::CaskLoader.load(cask_path("container-xar")),
|
||||
).to be_installed
|
||||
|
||||
expect(Hbc::Config.global.appdir.join("container")).to_not be_quarantined
|
||||
end
|
||||
|
||||
it "does not quarantine pure bzip2-based Casks" do
|
||||
Hbc::CLI::Install.run("container-bzip2", "--no-quarantine")
|
||||
|
||||
expect(
|
||||
Hbc::CaskLoader.load(cask_path("container-bzip2")),
|
||||
).to be_installed
|
||||
|
||||
expect(Hbc::Config.global.appdir.join("container")).to_not be_quarantined
|
||||
end
|
||||
|
||||
it "does not quarantine pure gzip-based Casks" do
|
||||
Hbc::CLI::Install.run("container-gzip", "--no-quarantine")
|
||||
|
||||
expect(
|
||||
Hbc::CaskLoader.load(cask_path("container-gzip")),
|
||||
).to be_installed
|
||||
|
||||
expect(Hbc::Config.global.appdir.join("container")).to_not be_quarantined
|
||||
end
|
||||
|
||||
it "does not quarantine the pkg in naked-pkg-based Casks" do
|
||||
Hbc::CLI::Install.run("container-pkg", "--no-quarantine")
|
||||
|
||||
naked_pkg = Hbc::CaskLoader.load(cask_path("container-pkg"))
|
||||
|
||||
expect(naked_pkg).to be_installed
|
||||
|
||||
expect(
|
||||
Hbc::Caskroom.path.join("container-pkg", naked_pkg.version, "container.pkg"),
|
||||
).to_not be_quarantined
|
||||
end
|
||||
|
||||
it "does not quarantine a nested container" do
|
||||
Hbc::CLI::Install.run("nested-app", "--no-quarantine")
|
||||
|
||||
expect(
|
||||
Hbc::CaskLoader.load(cask_path("nested-app")),
|
||||
).to be_installed
|
||||
|
||||
expect(Hbc::Config.global.appdir.join("MyNestedApp.app")).to_not be_quarantined
|
||||
end
|
||||
|
||||
it "does not quarantine Cask audits" do
|
||||
Hbc::CLI::Audit.run("local-transmission", "--download", "--no-quarantine")
|
||||
|
||||
local_transmission = Hbc::CaskLoader.load(cask_path("local-transmission"))
|
||||
cached_location = Hbc::Download.new(local_transmission, force: false, quarantine: false).perform
|
||||
|
||||
expect(cached_location).to_not be_quarantined
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,4 +1,5 @@
|
||||
cask 'local-transmission' do
|
||||
name 'Transmission'
|
||||
version '2.61'
|
||||
sha256 'e44ffa103fbf83f55c8d0b1bea309a43b2880798dae8620b1ee8da5e1095ec68'
|
||||
|
||||
|
||||
@ -90,7 +90,7 @@ module UnpackStrategy
|
||||
args: ["--bom", bomfile.path, "--", path, unpack_dir],
|
||||
verbose: verbose
|
||||
|
||||
FileUtils.chmod "u+w", Pathname.glob(unpack_dir/"**/*").reject(&:symlink?)
|
||||
FileUtils.chmod "u+w", Pathname.glob(unpack_dir/"**/*", File::FNM_DOTMATCH).reject(&:symlink?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -18,7 +18,7 @@ Homebrew\-Cask works robustly enough that we welcome new users, but the project
|
||||
.SH "FREQUENTLY USED COMMANDS"
|
||||
.
|
||||
.TP
|
||||
\fBinstall\fR [\-\-force] [\-\-skip\-cask\-deps] [\-\-require\-sha] [\-\-language=\fIiso\-language\fR[,\fIiso\-language\fR \.\.\. ]] \fItoken\fR [ \fItoken\fR \.\.\. ]
|
||||
\fBinstall\fR [\-\-force] [\-\-skip\-cask\-deps] [\-\-require\-sha] [\-\-no\-quarantine] [\-\-language=\fIiso\-language\fR[,\fIiso\-language\fR \.\.\. ]] \fItoken\fR [ \fItoken\fR \.\.\. ]
|
||||
Install Cask identified by \fItoken\fR\.
|
||||
.
|
||||
.TP
|
||||
@ -59,8 +59,8 @@ Check for configuration issues\. Can be useful to upload as a gist for developer
|
||||
Open the given Cask definition file for editing\.
|
||||
.
|
||||
.TP
|
||||
\fBfetch\fR [\-\-force] \fItoken\fR [ \fItoken\fR \.\.\. ]
|
||||
Download remote application files for the given Cask to the local cache\. With \fB\-\-force\fR, force re\-download even if the files are already cached\.
|
||||
\fBfetch\fR [\-\-force] [\-\-no\-quarantine] \fItoken\fR [ \fItoken\fR \.\.\. ]
|
||||
Download remote application files for the given Cask to the local cache\. With \fB\-\-force\fR, force re\-download even if the files are already cached\. \fB\-\-no\-quarantine\fR will prevent Gatekeeper from enforcing its security restrictions on the Cask\.
|
||||
.
|
||||
.TP
|
||||
\fBhome\fR or \fBhomepage\fR [ \fItoken\fR \.\.\. ]
|
||||
@ -74,8 +74,8 @@ With no arguments, display the project page \fIhttps://caskroom\.github\.io/\fR\
|
||||
Display information about the given Cask\.
|
||||
.
|
||||
.TP
|
||||
\fBinstall\fR [\-\-force] [\-\-skip\-cask\-deps] [\-\-require\-sha] \fItoken\fR [ \fItoken\fR \.\.\. ]
|
||||
Install the given Cask\. With \fB\-\-force\fR, re\-install even if the Cask appears to be already present\. With \fB\-\-skip\-cask\-deps\fR, skip any Cask dependencies\. \fB\-\-require\-sha\fR will abort installation if the Cask does not have a checksum defined\.
|
||||
\fBinstall\fR [\-\-force] [\-\-skip\-cask\-deps] [\-\-require\-sha] [\-\-no\-quarantine] \fItoken\fR [ \fItoken\fR \.\.\. ]
|
||||
Install the given Cask\. With \fB\-\-force\fR, re\-install even if the Cask appears to be already present\. With \fB\-\-skip\-cask\-deps\fR, skip any Cask dependencies\. \fB\-\-require\-sha\fR will abort installation if the Cask does not have a checksum defined\. \fB\-\-no\-quarantine\fR will prevent Gatekeeper from enforcing its security restrictions on the Cask\.
|
||||
.
|
||||
.IP
|
||||
\fItoken\fR is usually the ID of a Cask, but see \fIOTHER WAYS TO SPECIFY A CASK\fR for variations\.
|
||||
@ -98,7 +98,7 @@ Without token arguments, display all the installed Casks that have newer version
|
||||
\fB\-\-quiet\fR suppresses the display of versions\.
|
||||
.
|
||||
.TP
|
||||
\fBreinstall\fR \fItoken\fR [ \fItoken\fR \.\.\. ]
|
||||
\fBreinstall\fR [\-\-no\-quarantine] \fItoken\fR [ \fItoken\fR \.\.\. ]
|
||||
Reinstall the given Cask\.
|
||||
.
|
||||
.TP
|
||||
@ -158,6 +158,10 @@ Skip Cask dependencies when installing\.
|
||||
Abort Cask installation if the Cask does not have a checksum defined\.
|
||||
.
|
||||
.TP
|
||||
\fB\-\-no\-quarantine\fR
|
||||
Prevent Gatekeeper from enforcing its security restrictions on the Cask\. This will let you run it straightaway\.
|
||||
.
|
||||
.TP
|
||||
\fB\-\-verbose\fR
|
||||
Give additional feedback during installation\.
|
||||
.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user