From 7da0e025da120300a8ad94d4795f4784ebc0a345 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 1 Jun 2026 05:04:24 -0500 Subject: [PATCH] feat(updates): include SHA256 from sidecar files in Joomla updates.xml Read the .sha256 sidecar attachment (generated by GenerateReleaseChecksums) and populate the element in the update XML. This matches the pattern used by Akeeba (sha512) and JCE (sha256 + sha384 + sha512) for integrity verification. Also fix zip attachment filter to skip .sha256 sidecar files when selecting the download URL. Co-Authored-By: Claude Opus 4.6 (1M context) --- services/updateserver/joomla.go | 42 ++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/services/updateserver/joomla.go b/services/updateserver/joomla.go index 1c6cdb41a6..f56758c973 100644 --- a/services/updateserver/joomla.go +++ b/services/updateserver/joomla.go @@ -7,12 +7,14 @@ import ( "context" "encoding/xml" "fmt" + "io" "strings" "time" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses" repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo" + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/storage" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting" ) @@ -246,14 +248,25 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require continue } - // Find the first .zip attachment as the download URL. - var downloadURL string + // Find the first .zip attachment as the download URL, and its SHA256 sidecar. + var downloadURL, sha256Hash string + var zipName string for _, att := range rel.Attachments { - if strings.HasSuffix(strings.ToLower(att.Name), ".zip") { + if strings.HasSuffix(strings.ToLower(att.Name), ".zip") && !strings.HasSuffix(att.Name, ".sha256") { downloadURL = fmt.Sprintf("%s/releases/download/%s/%s", repoLink, rel.TagName, att.Name) + zipName = att.Name break } } + // Look for the SHA256 sidecar file. + if zipName != "" { + for _, att := range rel.Attachments { + if att.Name == zipName+".sha256" { + sha256Hash = readSHA256FromSidecar(ctx, att) + break + } + } + } // Fall back to the release tag archive if no zip attachment. if downloadURL == "" { downloadURL = fmt.Sprintf("%s/archive/%s.zip", repoLink, rel.TagName) @@ -302,6 +315,7 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require Maintainer: maintainer, MaintainerURL: maintainerURL, PHPMinimum: phpMinimum, + SHA256: sha256Hash, TargetPlatform: xmlTargetPlat{ Name: "joomla", Version: targetVersion, @@ -354,3 +368,25 @@ func channelSuffix(channel string) string { return "" } } + +// readSHA256FromSidecar reads the SHA256 hash from a .sha256 sidecar attachment. +// The sidecar format is: " \n" +func readSHA256FromSidecar(_ context.Context, att *repo_model.Attachment) string { + fr, err := storage.Attachments.Open(att.RelativePath()) + if err != nil { + return "" + } + defer fr.Close() + + data, err := io.ReadAll(io.LimitReader(fr, 256)) + if err != nil { + return "" + } + + content := strings.TrimSpace(string(data)) + // Format: "hexhash filename" + if idx := strings.Index(content, " "); idx > 0 { + return content[:idx] + } + return content +}