forgejo/modules/packages/rpm/metadata.go
Exploding Dragon ba4d0b8ffb
Support for grouping RPMs using paths ()
The current rpm repository places all packages in the same repository,
and different systems (el7,f34) may hit packages that do not belong to
this distribution (  ) , which now supports grouping of rpm.

![图片](https://github.com/go-gitea/gitea/assets/33776693/d1e1d99f-7799-4b2b-a19b-cb2a5c692914)

Fixes  .
Fixes  .

Refactor: [](https://github.com/go-gitea/gitea/pull/25866)
2024-01-12 03:16:05 +00:00

295 lines
8.3 KiB
Go

// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package rpm
import (
"fmt"
"io"
"strings"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/validation"
"github.com/sassoftware/go-rpmutils"
)
const (
PropertyMetadata = "rpm.metadata"
SettingKeyPrivate = "rpm.key.private"
SettingKeyPublic = "rpm.key.public"
RepositoryPackage = "_rpm"
RepositoryVersion = "_repository"
)
const (
// Can't use the syscall constants because they are not available for windows build.
sIFMT = 0xf000
sIFDIR = 0x4000
sIXUSR = 0x40
sIXGRP = 0x8
sIXOTH = 0x1
)
// https://rpm-software-management.github.io/rpm/manual/spec.html
// https://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/pkgformat.html
type Package struct {
Name string
Version string
VersionMetadata *VersionMetadata
FileMetadata *FileMetadata
}
type VersionMetadata struct {
License string `json:"license,omitempty"`
ProjectURL string `json:"project_url,omitempty"`
Summary string `json:"summary,omitempty"`
Description string `json:"description,omitempty"`
}
type FileMetadata struct {
Architecture string `json:"architecture,omitempty"`
Epoch string `json:"epoch,omitempty"`
Version string `json:"version,omitempty"`
Release string `json:"release,omitempty"`
Vendor string `json:"vendor,omitempty"`
Group string `json:"group,omitempty"`
Packager string `json:"packager,omitempty"`
SourceRpm string `json:"source_rpm,omitempty"`
BuildHost string `json:"build_host,omitempty"`
BuildTime uint64 `json:"build_time,omitempty"`
FileTime uint64 `json:"file_time,omitempty"`
InstalledSize uint64 `json:"installed_size,omitempty"`
ArchiveSize uint64 `json:"archive_size,omitempty"`
Provides []*Entry `json:"provide,omitempty"`
Requires []*Entry `json:"require,omitempty"`
Conflicts []*Entry `json:"conflict,omitempty"`
Obsoletes []*Entry `json:"obsolete,omitempty"`
Files []*File `json:"files,omitempty"`
Changelogs []*Changelog `json:"changelogs,omitempty"`
}
type Entry struct {
Name string `json:"name" xml:"name,attr"`
Flags string `json:"flags,omitempty" xml:"flags,attr,omitempty"`
Version string `json:"version,omitempty" xml:"ver,attr,omitempty"`
Epoch string `json:"epoch,omitempty" xml:"epoch,attr,omitempty"`
Release string `json:"release,omitempty" xml:"rel,attr,omitempty"`
}
type File struct {
Path string `json:"path" xml:",chardata"`
Type string `json:"type,omitempty" xml:"type,attr,omitempty"`
IsExecutable bool `json:"is_executable" xml:"-"`
}
type Changelog struct {
Author string `json:"author,omitempty" xml:"author,attr"`
Date timeutil.TimeStamp `json:"date,omitempty" xml:"date,attr"`
Text string `json:"text,omitempty" xml:",chardata"`
}
// ParsePackage parses the RPM package file
func ParsePackage(r io.Reader) (*Package, error) {
rpm, err := rpmutils.ReadRpm(r)
if err != nil {
return nil, err
}
nevra, err := rpm.Header.GetNEVRA()
if err != nil {
return nil, err
}
version := fmt.Sprintf("%s-%s", nevra.Version, nevra.Release)
if nevra.Epoch != "" && nevra.Epoch != "0" {
version = fmt.Sprintf("%s-%s", nevra.Epoch, version)
}
p := &Package{
Name: nevra.Name,
Version: version,
VersionMetadata: &VersionMetadata{
Summary: getString(rpm.Header, rpmutils.SUMMARY),
Description: getString(rpm.Header, rpmutils.DESCRIPTION),
License: getString(rpm.Header, rpmutils.LICENSE),
ProjectURL: getString(rpm.Header, rpmutils.URL),
},
FileMetadata: &FileMetadata{
Architecture: nevra.Arch,
Epoch: nevra.Epoch,
Version: nevra.Version,
Release: nevra.Release,
Vendor: getString(rpm.Header, rpmutils.VENDOR),
Group: getString(rpm.Header, rpmutils.GROUP),
Packager: getString(rpm.Header, rpmutils.PACKAGER),
SourceRpm: getString(rpm.Header, rpmutils.SOURCERPM),
BuildHost: getString(rpm.Header, rpmutils.BUILDHOST),
BuildTime: getUInt64(rpm.Header, rpmutils.BUILDTIME),
FileTime: getUInt64(rpm.Header, rpmutils.FILEMTIMES),
InstalledSize: getUInt64(rpm.Header, rpmutils.SIZE),
ArchiveSize: getUInt64(rpm.Header, rpmutils.SIG_PAYLOADSIZE),
Provides: getEntries(rpm.Header, rpmutils.PROVIDENAME, rpmutils.PROVIDEVERSION, rpmutils.PROVIDEFLAGS),
Requires: getEntries(rpm.Header, rpmutils.REQUIRENAME, rpmutils.REQUIREVERSION, rpmutils.REQUIREFLAGS),
Conflicts: getEntries(rpm.Header, 1054 /*rpmutils.CONFLICTNAME*/, 1055 /*rpmutils.CONFLICTVERSION*/, 1053 /*rpmutils.CONFLICTFLAGS*/), // https://github.com/sassoftware/go-rpmutils/pull/24
Obsoletes: getEntries(rpm.Header, rpmutils.OBSOLETENAME, rpmutils.OBSOLETEVERSION, rpmutils.OBSOLETEFLAGS),
Files: getFiles(rpm.Header),
Changelogs: getChangelogs(rpm.Header),
},
}
if !validation.IsValidURL(p.VersionMetadata.ProjectURL) {
p.VersionMetadata.ProjectURL = ""
}
return p, nil
}
func getString(h *rpmutils.RpmHeader, tag int) string {
values, err := h.GetStrings(tag)
if err != nil || len(values) < 1 {
return ""
}
return values[0]
}
func getUInt64(h *rpmutils.RpmHeader, tag int) uint64 {
values, err := h.GetUint64s(tag)
if err != nil || len(values) < 1 {
return 0
}
return values[0]
}
func getEntries(h *rpmutils.RpmHeader, namesTag, versionsTag, flagsTag int) []*Entry {
names, err := h.GetStrings(namesTag)
if err != nil || len(names) == 0 {
return nil
}
flags, err := h.GetUint64s(flagsTag)
if err != nil || len(flags) == 0 {
return nil
}
versions, err := h.GetStrings(versionsTag)
if err != nil || len(versions) == 0 {
return nil
}
if len(names) != len(flags) || len(names) != len(versions) {
return nil
}
entries := make([]*Entry, 0, len(names))
for i := range names {
e := &Entry{
Name: names[i],
}
flags := flags[i]
if (flags&rpmutils.RPMSENSE_GREATER) != 0 && (flags&rpmutils.RPMSENSE_EQUAL) != 0 {
e.Flags = "GE"
} else if (flags&rpmutils.RPMSENSE_LESS) != 0 && (flags&rpmutils.RPMSENSE_EQUAL) != 0 {
e.Flags = "LE"
} else if (flags & rpmutils.RPMSENSE_GREATER) != 0 {
e.Flags = "GT"
} else if (flags & rpmutils.RPMSENSE_LESS) != 0 {
e.Flags = "LT"
} else if (flags & rpmutils.RPMSENSE_EQUAL) != 0 {
e.Flags = "EQ"
}
version := versions[i]
if version != "" {
parts := strings.Split(version, "-")
versionParts := strings.Split(parts[0], ":")
if len(versionParts) == 2 {
e.Version = versionParts[1]
e.Epoch = versionParts[0]
} else {
e.Version = versionParts[0]
e.Epoch = "0"
}
if len(parts) > 1 {
e.Release = parts[1]
}
}
entries = append(entries, e)
}
return entries
}
func getFiles(h *rpmutils.RpmHeader) []*File {
baseNames, _ := h.GetStrings(rpmutils.BASENAMES)
dirNames, _ := h.GetStrings(rpmutils.DIRNAMES)
dirIndexes, _ := h.GetUint32s(rpmutils.DIRINDEXES)
fileFlags, _ := h.GetUint32s(rpmutils.FILEFLAGS)
fileModes, _ := h.GetUint32s(rpmutils.FILEMODES)
files := make([]*File, 0, len(baseNames))
for i := range baseNames {
if len(dirIndexes) <= i {
continue
}
dirIndex := dirIndexes[i]
if len(dirNames) <= int(dirIndex) {
continue
}
var fileType string
var isExecutable bool
if i < len(fileFlags) && (fileFlags[i]&rpmutils.RPMFILE_GHOST) != 0 {
fileType = "ghost"
} else if i < len(fileModes) {
if (fileModes[i] & sIFMT) == sIFDIR {
fileType = "dir"
} else {
mode := fileModes[i] & ^uint32(sIFMT)
isExecutable = (mode&sIXUSR) != 0 || (mode&sIXGRP) != 0 || (mode&sIXOTH) != 0
}
}
files = append(files, &File{
Path: dirNames[dirIndex] + baseNames[i],
Type: fileType,
IsExecutable: isExecutable,
})
}
return files
}
func getChangelogs(h *rpmutils.RpmHeader) []*Changelog {
texts, err := h.GetStrings(rpmutils.CHANGELOGTEXT)
if err != nil || len(texts) == 0 {
return nil
}
authors, err := h.GetStrings(rpmutils.CHANGELOGNAME)
if err != nil || len(authors) == 0 {
return nil
}
times, err := h.GetUint32s(rpmutils.CHANGELOGTIME)
if err != nil || len(times) == 0 {
return nil
}
if len(texts) != len(authors) || len(texts) != len(times) {
return nil
}
changelogs := make([]*Changelog, 0, len(texts))
for i := range texts {
changelogs = append(changelogs, &Changelog{
Author: authors[i],
Date: timeutil.TimeStamp(times[i]),
Text: texts[i],
})
}
return changelogs
}