Skip to content

Commit

Permalink
Checksum Ruby source file downloads
Browse files Browse the repository at this point in the history
  • Loading branch information
Bo98 committed Feb 21, 2023
1 parent c6e0ae8 commit 7f38157
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 35 deletions.
24 changes: 19 additions & 5 deletions Library/Homebrew/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,22 @@ def self.fetch_json_api_file(endpoint, target:)
end
end

sig { params(name: String, git_head: T.nilable(String)).returns(String) }
def self.fetch_homebrew_cask_source(name, git_head: nil)
sig { params(name: String, git_head: T.nilable(String), sha256: T.nilable(String)).returns(String) }
def self.fetch_homebrew_cask_source(name, git_head: nil, sha256: nil)
git_head = "master" if git_head.blank?
raw_endpoint = "#{git_head}/Casks/#{name}.rb"
return cache[raw_endpoint] if cache.present? && cache.key?(raw_endpoint)

# This API sometimes returns random 404s so needs a fallback at formulae.brew.sh.
raw_source_url = "https://raw.githubusercontent.com/Homebrew/homebrew-cask/#{raw_endpoint}"
api_source_url = "#{HOMEBREW_API_DEFAULT_DOMAIN}/cask-source/#{name}.rb"
output = Utils::Curl.curl_output("--fail", raw_source_url)

url = raw_source_url
output = Utils::Curl.curl_output("--fail", url)

if !output.success? || output.blank?
output = Utils::Curl.curl_output("--fail", api_source_url)
url = api_source_url
output = Utils::Curl.curl_output("--fail", url)
if !output.success? || output.blank?
raise ArgumentError, <<~EOS
No valid file found at either of:
Expand All @@ -127,7 +130,18 @@ def self.fetch_homebrew_cask_source(name, git_head: nil)
end
end

cache[raw_endpoint] = output.stdout
cask_source = output.stdout
actual_sha256 = Digest::SHA256.hexdigest(cask_source)
if sha256 && actual_sha256 != sha256
raise <<~EOS
SHA256 mismatch
Expected: #{Formatter.success(sha256.to_s)}
Actual: #{Formatter.error(actual_sha256.to_s)}
URL: #{url}
EOS
end

cache[raw_endpoint] = cask_source
end

sig { params(json: Hash).returns(Hash) }
Expand Down
6 changes: 3 additions & 3 deletions Library/Homebrew/api/cask.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ def fetch(token)
Homebrew::API.fetch "cask/#{token}.json"
end

sig { params(token: String, git_head: T.nilable(String)).returns(String) }
def fetch_source(token, git_head: nil)
Homebrew::API.fetch_homebrew_cask_source token, git_head: git_head
sig { params(token: String, git_head: T.nilable(String), sha256: T.nilable(String)).returns(String) }
def fetch_source(token, git_head: nil, sha256: nil)
Homebrew::API.fetch_homebrew_cask_source token, git_head: git_head, sha256: sha256
end

sig { returns(T::Boolean) }
Expand Down
51 changes: 30 additions & 21 deletions Library/Homebrew/cask/cask.rb
Original file line number Diff line number Diff line change
Expand Up @@ -277,27 +277,28 @@ def to_h
end

{
"token" => token,
"full_token" => full_name,
"tap" => tap&.name,
"name" => name,
"desc" => desc,
"homepage" => homepage,
"url" => url,
"appcast" => appcast,
"version" => version,
"versions" => os_versions,
"installed" => versions.last,
"outdated" => outdated?,
"sha256" => sha256,
"artifacts" => artifacts_list,
"caveats" => (caveats unless caveats.empty?),
"depends_on" => depends_on,
"conflicts_with" => conflicts_with,
"container" => container&.pairs,
"auto_updates" => auto_updates,
"tap_git_head" => tap&.git_head,
"languages" => languages,
"token" => token,
"full_token" => full_name,
"tap" => tap&.name,
"name" => name,
"desc" => desc,
"homepage" => homepage,
"url" => url,
"appcast" => appcast,
"version" => version,
"versions" => os_versions,
"installed" => versions.last,
"outdated" => outdated?,
"sha256" => sha256,
"artifacts" => artifacts_list,
"caveats" => (caveats unless caveats.empty?),
"depends_on" => depends_on,
"conflicts_with" => conflicts_with,
"container" => container&.pairs,
"auto_updates" => auto_updates,
"tap_git_head" => tap&.git_head,
"languages" => languages,
"ruby_source_checksum" => ruby_source_checksum,
}
end

Expand Down Expand Up @@ -349,6 +350,14 @@ def api_to_local_hash(hash)
hash
end

def ruby_source_checksum
return JSON.parse(@source)["ruby_source_checksum"] if loaded_from_api

{
"sha256" => Digest::SHA256.file(sourcefile_path).hexdigest,
}
end

def artifacts_list
artifacts.map do |artifact|
case artifact
Expand Down
4 changes: 3 additions & 1 deletion Library/Homebrew/cask/cask_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,9 @@ def load(config:)
# Use the cask-source API if there are any `*flight` blocks or the cask has multiple languages
if json_cask[:artifacts].any? { |artifact| FLIGHT_STANZAS.include?(artifact.keys.first) } ||
json_cask[:languages].any?
cask_source = Homebrew::API::Cask.fetch_source(token, git_head: json_cask[:tap_git_head])
cask_source = Homebrew::API::Cask.fetch_source(token,
git_head: json_cask[:tap_git_head],
sha256: json_cask.dig(:ruby_source_checksum, :sha256))
return FromContentLoader.new(cask_source).load(config: config)
end

Expand Down
11 changes: 11 additions & 0 deletions Library/Homebrew/formula.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2133,6 +2133,7 @@ def to_hash
"disable_date" => disable_date,
"disable_reason" => disable_reason,
"tap_git_head" => tap_git_head,
"ruby_source_checksum" => {},
}

if stable
Expand Down Expand Up @@ -2183,6 +2184,16 @@ def to_hash
}
end

if self.class.loaded_from_api && active_spec.resource_defined?("ruby-source")
hsh["ruby_source_checksum"] = {
"sha256" => resource("ruby-source").checksum.hexdigest,
}
elsif !self.class.loaded_from_api && path.exist?
hsh["ruby_source_checksum"] = {
"sha256" => Digest::SHA256.file(path).hexdigest,
}
end

hsh
end

Expand Down
2 changes: 1 addition & 1 deletion Library/Homebrew/formula.rbi
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Formula
def service?; end
def version; end

def resource; end
def resource(name); end
def deps; end
def uses_from_macos_elements; end
def requirements; end
Expand Down
8 changes: 5 additions & 3 deletions Library/Homebrew/formula_installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1182,9 +1182,11 @@ def fetch
if pour_bottle?(output_warning: true)
formula.fetch_bottle_tab
else
if formula.core_formula? && Homebrew::EnvConfig.install_from_api?
url = "https://raw.githubusercontent.com/#{formula.tap.full_name}/#{formula.tap_git_head}/Formula/#{formula.name}.rb"
@formula = Formulary.factory(url, formula.active_spec_sym,
if formula.class.loaded_from_api
resource = formula.resource("ruby-source")
resource.fetch
@formula = Formulary.factory(resource.cached_download,
formula.active_spec_sym,
alias_path: formula.alias_path,
flags: formula.class.build_flags,
from: :formula_installer)
Expand Down
11 changes: 10 additions & 1 deletion Library/Homebrew/formulary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,13 @@ def self.load_formula_from_api(name, flags:)
link_overwrite overwrite_path
end

if (ruby_source_sha256 = json_formula.dig("ruby_source_checksum", "sha256")).present?
resource "ruby-source" do
url "https://raw.githubusercontent.com/Homebrew/homebrew-core/#{json_formula["tap_git_head"]}/Formula/#{name}.rb"
sha256 ruby_source_sha256
end
end

def install
raise "Cannot build from source from abstract formula."
end
Expand Down Expand Up @@ -446,7 +453,9 @@ def initialize(alias_path)
class FromPathLoader < FormulaLoader
def initialize(path)
path = Pathname.new(path).expand_path
super path.basename(".rb").to_s, path
name = path.basename(".rb").to_s
name = name.split("--", 2).last if path.dirname == HOMEBREW_CACHE/"downloads"
super name, path
end
end

Expand Down

0 comments on commit 7f38157

Please sign in to comment.