feat(updates): include SHA256 from sidecar files in Joomla updates.xml

Read the .sha256 sidecar attachment (generated by
GenerateReleaseChecksums) and populate the <sha256> 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) <noreply@anthropic.com>
This commit is contained in:
Jonathan Miller
2026-06-01 05:04:24 -05:00
parent 0e09723d2a
commit 7da0e025da
+39 -3
View File
@@ -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: "<hex> <filename>\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
}