From 05f07953fe3c2d32b2e831e4c14b0ef368da0428 Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Sat, 13 Jul 2024 16:28:21 -0400 Subject: [PATCH 1/2] Create `Resource::BottleManifest`. --- Library/Homebrew/resource.rb | 47 +++++++++++++++++++++++++++++++ Library/Homebrew/software_spec.rb | 41 ++------------------------- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/Library/Homebrew/resource.rb b/Library/Homebrew/resource.rb index 4fb883edbb064..4c196d173d38d 100644 --- a/Library/Homebrew/resource.rb +++ b/Library/Homebrew/resource.rb @@ -267,6 +267,53 @@ def stage(target, &block) end end + # A resource for a bottle manifest. + class BottleManifest < Resource + attr_reader :bottle + + def initialize(bottle) + super("#{bottle.name}_bottle_manifest") + @bottle = bottle + end + + def verify_download_integrity(_filename) + # no-op + end + + def tab + json = begin + JSON.parse(cached_download.read) + rescue JSON::ParserError + raise "The downloaded GitHub Packages manifest was corrupted or modified (it is not valid JSON): " \ + "\n#{cached_download}" + end + + manifests = json["manifests"] + raise ArgumentError, "Missing 'manifests' section." if manifests.blank? + + manifests_annotations = manifests.filter_map { |m| m["annotations"] } + raise ArgumentError, "Missing 'annotations' section." if manifests_annotations.blank? + + bottle_digest = bottle.resource.checksum.hexdigest + image_ref = GitHubPackages.version_rebuild(bottle.resource.version, bottle.rebuild, bottle.tag.to_s) + manifest_annotations = manifests_annotations.find do |m| + next if m["sh.brew.bottle.digest"] != bottle_digest + + m["org.opencontainers.image.ref.name"] == image_ref + end + raise ArgumentError, "Couldn't find manifest matching bottle checksum." if manifest_annotations.blank? + + tab = manifest_annotations["sh.brew.tab"] + raise ArgumentError, "Couldn't find tab from manifest." if tab.blank? + + begin + JSON.parse(tab) + rescue JSON::ParserError + raise ArgumentError, "Couldn't parse tab JSON." + end + end + end + # A resource containing a patch. class PatchResource < Resource attr_reader :patch_files diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb index f404206f9d29b..97d2678045821 100644 --- a/Library/Homebrew/software_spec.rb +++ b/Library/Homebrew/software_spec.rb @@ -396,7 +396,7 @@ def fetch_tab github_packages_manifest_resource.fetch(verify_download_integrity: false) begin - github_packages_manifest_resource_tab(github_packages_manifest_resource) + github_packages_manifest_resource.tab rescue RuntimeError => e raise DownloadError.new(github_packages_manifest_resource, e) end @@ -415,7 +415,7 @@ def fetch_tab def tab_attributes return {} unless github_packages_manifest_resource&.downloaded? - github_packages_manifest_resource_tab(github_packages_manifest_resource) + github_packages_manifest_resource.tab end sig { returns(Filename) } @@ -425,46 +425,11 @@ def filename private - def github_packages_manifest_resource_tab(github_packages_manifest_resource) - manifest_json = github_packages_manifest_resource.cached_download.read - - json = begin - JSON.parse(manifest_json) - rescue JSON::ParserError - raise "The downloaded GitHub Packages manifest was corrupted or modified (it is not valid JSON): " \ - "\n#{github_packages_manifest_resource.cached_download}" - end - - manifests = json["manifests"] - raise ArgumentError, "Missing 'manifests' section." if manifests.blank? - - manifests_annotations = manifests.filter_map { |m| m["annotations"] } - raise ArgumentError, "Missing 'annotations' section." if manifests_annotations.blank? - - bottle_digest = @resource.checksum.hexdigest - image_ref = GitHubPackages.version_rebuild(@resource.version, rebuild, @tag.to_s) - manifest_annotations = manifests_annotations.find do |m| - next if m["sh.brew.bottle.digest"] != bottle_digest - - m["org.opencontainers.image.ref.name"] == image_ref - end - raise ArgumentError, "Couldn't find manifest matching bottle checksum." if manifest_annotations.blank? - - tab = manifest_annotations["sh.brew.tab"] - raise ArgumentError, "Couldn't find tab from manifest." if tab.blank? - - begin - JSON.parse(tab) - rescue JSON::ParserError - raise ArgumentError, "Couldn't parse tab JSON." - end - end - def github_packages_manifest_resource return if @resource.download_strategy != CurlGitHubPackagesDownloadStrategy @github_packages_manifest_resource ||= begin - resource = Resource.new("#{name}_bottle_manifest") + resource = Resource::BottleManifest.new(self) version_rebuild = GitHubPackages.version_rebuild(@resource.version, rebuild) resource.version(version_rebuild) From ae6f43921ae0389973fe62b0f74d11ba6bbbd31b Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Sat, 13 Jul 2024 16:50:53 -0400 Subject: [PATCH 2/2] Implement `verify_download_integrity` for bottle manifests. --- Library/Homebrew/resource.rb | 21 +++++++++++++-------- Library/Homebrew/software_spec.rb | 9 +-------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Library/Homebrew/resource.rb b/Library/Homebrew/resource.rb index 4c196d173d38d..7c4d6e773e3ea 100644 --- a/Library/Homebrew/resource.rb +++ b/Library/Homebrew/resource.rb @@ -269,6 +269,8 @@ def stage(target, &block) # A resource for a bottle manifest. class BottleManifest < Resource + class Error < RuntimeError; end + attr_reader :bottle def initialize(bottle) @@ -277,22 +279,25 @@ def initialize(bottle) end def verify_download_integrity(_filename) - # no-op + # We don't have a checksum, but we can at least try parsing it. + tab + rescue Error => e + raise DownloadError.new(self, e) end def tab json = begin JSON.parse(cached_download.read) rescue JSON::ParserError - raise "The downloaded GitHub Packages manifest was corrupted or modified (it is not valid JSON): " \ - "\n#{cached_download}" + raise Error, "The downloaded GitHub Packages manifest was corrupted or modified (it is not valid JSON): " \ + "\n#{cached_download}" end manifests = json["manifests"] - raise ArgumentError, "Missing 'manifests' section." if manifests.blank? + raise Error, "Missing 'manifests' section." if manifests.blank? manifests_annotations = manifests.filter_map { |m| m["annotations"] } - raise ArgumentError, "Missing 'annotations' section." if manifests_annotations.blank? + raise Error, "Missing 'annotations' section." if manifests_annotations.blank? bottle_digest = bottle.resource.checksum.hexdigest image_ref = GitHubPackages.version_rebuild(bottle.resource.version, bottle.rebuild, bottle.tag.to_s) @@ -301,15 +306,15 @@ def tab m["org.opencontainers.image.ref.name"] == image_ref end - raise ArgumentError, "Couldn't find manifest matching bottle checksum." if manifest_annotations.blank? + raise Error, "Couldn't find manifest matching bottle checksum." if manifest_annotations.blank? tab = manifest_annotations["sh.brew.tab"] - raise ArgumentError, "Couldn't find tab from manifest." if tab.blank? + raise Error, "Couldn't find tab from manifest." if tab.blank? begin JSON.parse(tab) rescue JSON::ParserError - raise ArgumentError, "Couldn't parse tab JSON." + raise Error, "Couldn't parse tab JSON." end end end diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb index 97d2678045821..63e9e1b89778c 100644 --- a/Library/Homebrew/software_spec.rb +++ b/Library/Homebrew/software_spec.rb @@ -392,14 +392,7 @@ def stage def fetch_tab return if github_packages_manifest_resource.blank? - # a checksum is used later identifying the correct tab but we do not have the checksum for the manifest/tab - github_packages_manifest_resource.fetch(verify_download_integrity: false) - - begin - github_packages_manifest_resource.tab - rescue RuntimeError => e - raise DownloadError.new(github_packages_manifest_resource, e) - end + github_packages_manifest_resource.fetch rescue DownloadError raise unless fallback_on_error