|
| 1 | +# typed: strict |
| 2 | +# frozen_string_literal: true |
| 3 | + |
| 4 | +require "os/mac/ffi" |
| 5 | + |
| 6 | +module OS |
| 7 | + module Mac |
| 8 | + module Cask |
| 9 | + module Quarantine |
| 10 | + COPY_XATTRS_RUBY = "require \"os/mac/ffi\"; MacOS::FFI.copy_xattrs(ARGV.fetch(0), ARGV.fetch(1))" |
| 11 | + |
| 12 | + module ClassMethods |
| 13 | + extend T::Helpers |
| 14 | + include Kernel |
| 15 | + include ::Utils::Output::Mixin |
| 16 | + |
| 17 | + requires_ancestor { ::Cask::Quarantine } |
| 18 | + |
| 19 | + sig { returns([Symbol, T.nilable(String)]) } |
| 20 | + def check_quarantine_support |
| 21 | + return super unless ffi_quarantine? |
| 22 | + |
| 23 | + odebug "Checking quarantine support" |
| 24 | + |
| 25 | + status = if ::Cask::Quarantine.xattr_available? |
| 26 | + odebug "Quarantine is available via FFI." |
| 27 | + :quarantine_available |
| 28 | + else |
| 29 | + odebug "There's no working version of `xattr` on this system." |
| 30 | + :xattr_broken |
| 31 | + end |
| 32 | + [status, nil] |
| 33 | + end |
| 34 | + |
| 35 | + sig { params(cask: T.nilable(::Cask::Cask), download_path: T.nilable(::Pathname), action: T::Boolean).void } |
| 36 | + def cask!(cask: nil, download_path: nil, action: true) |
| 37 | + return super unless ffi_quarantine? |
| 38 | + return if cask.nil? || download_path.nil? |
| 39 | + |
| 40 | + return if ::Cask::Quarantine.detect(download_path) |
| 41 | + |
| 42 | + odebug "Quarantining #{download_path}" |
| 43 | + |
| 44 | + path_cf_string = MacOS::FFI::CoreFoundation.string_create(download_path.to_s) |
| 45 | + if path_cf_string.null? |
| 46 | + Kernel.raise ::Cask::CaskQuarantineError.new(download_path, |
| 47 | + "Failed to create CFString for path") |
| 48 | + end |
| 49 | + |
| 50 | + path_cf_url = MacOS::FFI::CoreFoundation.url_create_with_file_system_path(path_cf_string) |
| 51 | + if path_cf_url.null? |
| 52 | + Kernel.raise ::Cask::CaskQuarantineError.new(download_path, |
| 53 | + "Failed to create CFURL for path") |
| 54 | + end |
| 55 | + |
| 56 | + quarantine_agent_name = MacOS::FFI::CoreFoundation.string_create("Homebrew Cask") |
| 57 | + quarantine_data_url = MacOS::FFI::CoreFoundation.string_create(cask.url.to_s) |
| 58 | + quarantine_origin_url = MacOS::FFI::CoreFoundation.string_create(cask.homepage.to_s) |
| 59 | + if quarantine_agent_name.null? || quarantine_data_url.null? || quarantine_origin_url.null? |
| 60 | + Kernel.raise ::Cask::CaskQuarantineError.new(download_path, |
| 61 | + "Failed to create CFString for quarantine properties") |
| 62 | + end |
| 63 | + |
| 64 | + quarantine_dictionary = MacOS::FFI::CoreFoundation.dictionary_create( |
| 65 | + MacOS::FFI::LaunchServices.quarantine_agent_name_key => quarantine_agent_name, |
| 66 | + MacOS::FFI::LaunchServices.quarantine_type_key => MacOS::FFI::LaunchServices.quarantine_type_web_download, |
| 67 | + MacOS::FFI::LaunchServices.quarantine_data_url_key => quarantine_data_url, |
| 68 | + MacOS::FFI::LaunchServices.quarantine_origin_url_key => quarantine_origin_url, |
| 69 | + ) |
| 70 | + if quarantine_dictionary.null? |
| 71 | + Kernel.raise ::Cask::CaskQuarantineError.new(download_path, "Failed to create quarantine dictionary") |
| 72 | + end |
| 73 | + |
| 74 | + success = MacOS::FFI::CoreFoundation.url_set_resource_property_for_key( |
| 75 | + path_cf_url, |
| 76 | + MacOS::FFI::CoreFoundation.url_quarantine_properties_key, |
| 77 | + quarantine_dictionary, |
| 78 | + ) |
| 79 | + |
| 80 | + return if success |
| 81 | + |
| 82 | + Kernel.raise ::Cask::CaskQuarantineError.new(download_path, "Failed to set quarantine properties for URL") |
| 83 | + end |
| 84 | + |
| 85 | + sig { params(from: ::Pathname, to: ::Pathname, command: T.class_of(::SystemCommand)).void } |
| 86 | + def copy_xattrs(from, to, command:) |
| 87 | + odebug "Copying xattrs from #{from} to #{to}" |
| 88 | + return super unless ffi_quarantine? |
| 89 | + |
| 90 | + if to.writable? |
| 91 | + MacOS::FFI.copy_xattrs(from.to_s, to.to_s) |
| 92 | + return |
| 93 | + end |
| 94 | + |
| 95 | + command.run!( |
| 96 | + HOMEBREW_BREW_FILE, |
| 97 | + args: [ |
| 98 | + "ruby", |
| 99 | + "--", |
| 100 | + "-e", |
| 101 | + COPY_XATTRS_RUBY, |
| 102 | + from, |
| 103 | + to, |
| 104 | + ], |
| 105 | + sudo: true, |
| 106 | + ) |
| 107 | + end |
| 108 | + |
| 109 | + private |
| 110 | + |
| 111 | + sig { returns(T::Boolean) } |
| 112 | + def ffi_quarantine? |
| 113 | + # TODO: Expand FFI quarantine and xattr copying to all users when fully working. |
| 114 | + Homebrew::EnvConfig.developer? |
| 115 | + end |
| 116 | + end |
| 117 | + end |
| 118 | + end |
| 119 | + end |
| 120 | +end |
| 121 | + |
| 122 | +Cask::Quarantine.singleton_class.prepend(OS::Mac::Cask::Quarantine::ClassMethods) |
0 commit comments