mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Add RPM registry (#23380)
Fixes #20751 This PR adds a RPM package registry. You can follow [this tutorial](https://opensource.com/article/18/9/how-build-rpm-packages) to build a *.rpm package for testing. This functionality is similar to the Debian registry (#22854) and therefore shares some methods. I marked this PR as blocked because it should be merged after #22854. 
This commit is contained in:
		
							
								
								
									
										296
									
								
								modules/packages/rpm/metadata.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								modules/packages/rpm/metadata.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,296 @@ | ||||
| // 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.metdata" | ||||
|  | ||||
| 	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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user