| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- // 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"
- PropertyGroup = "rpm.group"
- PropertyArchitecture = "rpm.architecture"
-
- 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, rpmutils.CONFLICTNAME, rpmutils.CONFLICTVERSION, rpmutils.CONFLICTFLAGS),
- 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
- }
|