From e7f667c19879114524719b1c6fda9db974d484a4 Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Fri, 6 Jun 2025 14:18:56 +0100 Subject: [PATCH] cask_loader: improve error handling. Handle weird edge cases where we try to read a cask from invalid paths. --- Library/Homebrew/cask/cask_loader.rb | 29 ++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/Library/Homebrew/cask/cask_loader.rb b/Library/Homebrew/cask/cask_loader.rb index bdf4bded72..24befa53a3 100644 --- a/Library/Homebrew/cask/cask_loader.rb +++ b/Library/Homebrew/cask/cask_loader.rb @@ -6,6 +6,7 @@ require "cask/cask" require "uri" require "utils/curl" require "extend/hash/keys" +require "api" module Cask # Loads a cask from various sources. @@ -104,8 +105,8 @@ module Cask return end - return if %w[.rb .json].exclude?(path.extname) return unless path.expand_path.exist? + return if invalid_path?(path) return if Homebrew::EnvConfig.forbid_packages_from_paths? && !path.realpath.to_s.start_with?("#{Caskroom.path}/", "#{HOMEBREW_LIBRARY}/Taps/") @@ -113,6 +114,14 @@ module Cask new(path) end + sig { params(pathname: Pathname, valid_extnames: T::Array[String]).returns(T::Boolean) } + def self.invalid_path?(pathname, valid_extnames: %w[.rb .json]) + return true if valid_extnames.exclude?(pathname.extname) + + @invalid_basenames ||= %w[INSTALL_RECEIPT.json sbom.spdx.json].freeze + @invalid_basenames.include?(pathname.basename.to_s) + end + attr_reader :token, :path sig { params(path: T.any(Pathname, String), token: String).void } @@ -135,8 +144,10 @@ module Cask @content = path.read(encoding: "UTF-8") @config = config - if path.extname == ".json" - return FromAPILoader.new(token, from_json: JSON.parse(@content), path:).load(config:) + if !self.class.invalid_path?(path, valid_extnames: %w[.json]) && + (from_json = JSON.parse(@content).presence) && + from_json.is_a?(Hash) + return FromAPILoader.new(token, from_json:, path:).load(config:) end begin @@ -284,7 +295,7 @@ module Cask sig { returns(Pathname) } attr_reader :path - sig { returns(T.nilable(Hash)) } + sig { returns(T.nilable(T::Hash[T.any(String, Symbol), T.anything])) } attr_reader :from_json sig { @@ -306,7 +317,13 @@ module Cask new("#{tap}/#{token}") end - sig { params(token: String, from_json: Hash, path: T.nilable(Pathname)).void } + sig { + params( + token: String, + from_json: T.nilable(T::Hash[T.any(String, Symbol), T.anything]), + path: T.nilable(Pathname), + ).void + } def initialize(token, from_json: T.unsafe(nil), path: nil) @token = token.sub(%r{^homebrew/(?:homebrew-)?cask/}i, "") @sourcefile_path = path || Homebrew::API::Cask.cached_json_file_path @@ -400,7 +417,7 @@ module Cask container(**container_hash) end - json_cask[:artifacts].each do |artifact| + json_cask[:artifacts]&.each do |artifact| # convert generic string replacements into actual ones artifact = cask.loader.from_h_gsubs(artifact, appdir) key = artifact.keys.first