diff --git a/Library/Homebrew/cask/cask_loader.rb b/Library/Homebrew/cask/cask_loader.rb index 1c20582efe60b..58c10b944ddcd 100644 --- a/Library/Homebrew/cask/cask_loader.rb +++ b/Library/Homebrew/cask/cask_loader.rb @@ -12,6 +12,9 @@ module Cask module CaskLoader extend Context + ALLOWED_URL_SCHEMES = %w[file].freeze + private_constant :ALLOWED_URL_SCHEMES + module ILoader extend T::Helpers interface! @@ -171,17 +174,25 @@ def self.try_new(ref, warn: false) new(uri) end - attr_reader :url + attr_reader :url, :name sig { params(url: T.any(URI::Generic, String)).void } def initialize(url) @url = URI(url) - super Cache.path/File.basename(T.must(@url.path)) + @name = File.basename(T.must(@url.path)) + super Cache.path/name end def load(config:) path.dirname.mkpath + if ALLOWED_URL_SCHEMES.exclude?(url.scheme) + raise UnsupportedInstallationMethod, + "Non-checksummed download of #{name} formula file from an arbitrary URL is unsupported! " \ + "`brew extract` or `brew create` and `brew tap-new` to create a formula file in a tap " \ + "on GitHub instead." + end + begin ohai "Downloading #{url}" ::Utils::Curl.curl_download url, to: path diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index ae7e9c11887ad..524634f1a996d 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -17,8 +17,8 @@ module Formulary extend Context extend Cachable - URL_START_REGEX = %r{(https?|ftp|file)://} - private_constant :URL_START_REGEX + ALLOWED_URL_SCHEMES = %w[file].freeze + private_constant :ALLOWED_URL_SCHEMES # `:codesign` and custom requirement classes are not supported. API_SUPPORTED_REQUIREMENTS = [:arch, :linux, :macos, :maximum_macos, :xcode].freeze @@ -696,7 +696,7 @@ class FromURILoader < FormulaLoader def self.try_new(ref, from: T.unsafe(nil), warn: false) ref = ref.to_s - new(ref, from:) if URL_START_REGEX.match?(ref) + new(ref, from:) if URI(ref).scheme.present? end attr_reader :url @@ -713,12 +713,8 @@ def initialize(url, from: nil) end def load_file(flags:, ignore_errors:) - match = url.match(%r{githubusercontent.com/[\w-]+/[\w-]+/[a-f0-9]{40}(?:/Formula)?/(?[\w+-.@]+).rb}) - if match - raise UnsupportedInstallationMethod, - "Installation of #{match[:name]} from a GitHub commit URL is unsupported! " \ - "`brew extract #{match[:name]}` to a stable tap on GitHub instead." - elsif url.match?(%r{^(https?|ftp)://}) + url_scheme = URI(url).scheme + if ALLOWED_URL_SCHEMES.exclude?(url_scheme) raise UnsupportedInstallationMethod, "Non-checksummed download of #{name} formula file from an arbitrary URL is unsupported! " \ "`brew extract` or `brew create` and `brew tap-new` to create a formula file in a tap " \ diff --git a/Library/Homebrew/test/cask/cask_loader/from_uri_loader_spec.rb b/Library/Homebrew/test/cask/cask_loader/from_uri_loader_spec.rb index e4126b20b068e..1dc49aa1cf51e 100644 --- a/Library/Homebrew/test/cask/cask_loader/from_uri_loader_spec.rb +++ b/Library/Homebrew/test/cask/cask_loader/from_uri_loader_spec.rb @@ -18,4 +18,34 @@ RUBY end end + + describe "::load" do + it "raises an error when given an https URL" do + loader = described_class.new("https://brew.sh/foo.rb") + expect do + loader.load(config: nil) + end.to raise_error(UnsupportedInstallationMethod) + end + + it "raises an error when given an ftp URL" do + loader = described_class.new("ftp://brew.sh/foo.rb") + expect do + loader.load(config: nil) + end.to raise_error(UnsupportedInstallationMethod) + end + + it "raises an error when given an sftp URL" do + loader = described_class.new("sftp://brew.sh/foo.rb") + expect do + loader.load(config: nil) + end.to raise_error(UnsupportedInstallationMethod) + end + + it "does not raise an error when given a file URL" do + loader = described_class.new("file://#{TEST_FIXTURE_DIR}/cask/Casks/local-caffeine.rb") + expect do + loader.load(config: nil) + end.not_to raise_error(UnsupportedInstallationMethod) + end + end end diff --git a/Library/Homebrew/test/formulary_spec.rb b/Library/Homebrew/test/formulary_spec.rb index 86566a72b7111..384fa622f75c9 100644 --- a/Library/Homebrew/test/formulary_spec.rb +++ b/Library/Homebrew/test/formulary_spec.rb @@ -523,6 +523,38 @@ def formula_json_contents(extra_items = {}) end end end + + context "when passed a URL" do + it "raises an error when given an https URL" do + expect do + described_class.factory("https://brew.sh/foo.rb") + end.to raise_error(UnsupportedInstallationMethod) + end + + it "raises an error when given a bottle URL" do + expect do + described_class.factory("https://brew.sh/foo-1.0.arm64_catalina.bottle.tar.gz") + end.to raise_error(UnsupportedInstallationMethod) + end + + it "raises an error when given an ftp URL" do + expect do + described_class.factory("ftp://brew.sh/foo.rb") + end.to raise_error(UnsupportedInstallationMethod) + end + + it "raises an error when given an sftp URL" do + expect do + described_class.factory("sftp://brew.sh/foo.rb") + end.to raise_error(UnsupportedInstallationMethod) + end + + it "does not raise an error when given a file URL" do + expect do + described_class.factory("file://#{TEST_FIXTURE_DIR}/testball.rb") + end.not_to raise_error(UnsupportedInstallationMethod) + end + end end specify "::from_contents" do