code.gitea.io/gitea@v1.22.3/modules/packages/rpm/metadata.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package rpm 5 6 import ( 7 "fmt" 8 "io" 9 "strings" 10 11 "code.gitea.io/gitea/modules/timeutil" 12 "code.gitea.io/gitea/modules/validation" 13 14 "github.com/sassoftware/go-rpmutils" 15 ) 16 17 const ( 18 PropertyMetadata = "rpm.metadata" 19 PropertyGroup = "rpm.group" 20 PropertyArchitecture = "rpm.architecture" 21 22 SettingKeyPrivate = "rpm.key.private" 23 SettingKeyPublic = "rpm.key.public" 24 25 RepositoryPackage = "_rpm" 26 RepositoryVersion = "_repository" 27 ) 28 29 const ( 30 // Can't use the syscall constants because they are not available for windows build. 31 sIFMT = 0xf000 32 sIFDIR = 0x4000 33 sIXUSR = 0x40 34 sIXGRP = 0x8 35 sIXOTH = 0x1 36 ) 37 38 // https://rpm-software-management.github.io/rpm/manual/spec.html 39 // https://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/pkgformat.html 40 41 type Package struct { 42 Name string 43 Version string 44 VersionMetadata *VersionMetadata 45 FileMetadata *FileMetadata 46 } 47 48 type VersionMetadata struct { 49 License string `json:"license,omitempty"` 50 ProjectURL string `json:"project_url,omitempty"` 51 Summary string `json:"summary,omitempty"` 52 Description string `json:"description,omitempty"` 53 } 54 55 type FileMetadata struct { 56 Architecture string `json:"architecture,omitempty"` 57 Epoch string `json:"epoch,omitempty"` 58 Version string `json:"version,omitempty"` 59 Release string `json:"release,omitempty"` 60 Vendor string `json:"vendor,omitempty"` 61 Group string `json:"group,omitempty"` 62 Packager string `json:"packager,omitempty"` 63 SourceRpm string `json:"source_rpm,omitempty"` 64 BuildHost string `json:"build_host,omitempty"` 65 BuildTime uint64 `json:"build_time,omitempty"` 66 FileTime uint64 `json:"file_time,omitempty"` 67 InstalledSize uint64 `json:"installed_size,omitempty"` 68 ArchiveSize uint64 `json:"archive_size,omitempty"` 69 70 Provides []*Entry `json:"provide,omitempty"` 71 Requires []*Entry `json:"require,omitempty"` 72 Conflicts []*Entry `json:"conflict,omitempty"` 73 Obsoletes []*Entry `json:"obsolete,omitempty"` 74 75 Files []*File `json:"files,omitempty"` 76 77 Changelogs []*Changelog `json:"changelogs,omitempty"` 78 } 79 80 type Entry struct { 81 Name string `json:"name" xml:"name,attr"` 82 Flags string `json:"flags,omitempty" xml:"flags,attr,omitempty"` 83 Version string `json:"version,omitempty" xml:"ver,attr,omitempty"` 84 Epoch string `json:"epoch,omitempty" xml:"epoch,attr,omitempty"` 85 Release string `json:"release,omitempty" xml:"rel,attr,omitempty"` 86 } 87 88 type File struct { 89 Path string `json:"path" xml:",chardata"` 90 Type string `json:"type,omitempty" xml:"type,attr,omitempty"` 91 IsExecutable bool `json:"is_executable" xml:"-"` 92 } 93 94 type Changelog struct { 95 Author string `json:"author,omitempty" xml:"author,attr"` 96 Date timeutil.TimeStamp `json:"date,omitempty" xml:"date,attr"` 97 Text string `json:"text,omitempty" xml:",chardata"` 98 } 99 100 // ParsePackage parses the RPM package file 101 func ParsePackage(r io.Reader) (*Package, error) { 102 rpm, err := rpmutils.ReadRpm(r) 103 if err != nil { 104 return nil, err 105 } 106 107 nevra, err := rpm.Header.GetNEVRA() 108 if err != nil { 109 return nil, err 110 } 111 112 version := fmt.Sprintf("%s-%s", nevra.Version, nevra.Release) 113 if nevra.Epoch != "" && nevra.Epoch != "0" { 114 version = fmt.Sprintf("%s-%s", nevra.Epoch, version) 115 } 116 117 p := &Package{ 118 Name: nevra.Name, 119 Version: version, 120 VersionMetadata: &VersionMetadata{ 121 Summary: getString(rpm.Header, rpmutils.SUMMARY), 122 Description: getString(rpm.Header, rpmutils.DESCRIPTION), 123 License: getString(rpm.Header, rpmutils.LICENSE), 124 ProjectURL: getString(rpm.Header, rpmutils.URL), 125 }, 126 FileMetadata: &FileMetadata{ 127 Architecture: nevra.Arch, 128 Epoch: nevra.Epoch, 129 Version: nevra.Version, 130 Release: nevra.Release, 131 Vendor: getString(rpm.Header, rpmutils.VENDOR), 132 Group: getString(rpm.Header, rpmutils.GROUP), 133 Packager: getString(rpm.Header, rpmutils.PACKAGER), 134 SourceRpm: getString(rpm.Header, rpmutils.SOURCERPM), 135 BuildHost: getString(rpm.Header, rpmutils.BUILDHOST), 136 BuildTime: getUInt64(rpm.Header, rpmutils.BUILDTIME), 137 FileTime: getUInt64(rpm.Header, rpmutils.FILEMTIMES), 138 InstalledSize: getUInt64(rpm.Header, rpmutils.SIZE), 139 ArchiveSize: getUInt64(rpm.Header, rpmutils.SIG_PAYLOADSIZE), 140 141 Provides: getEntries(rpm.Header, rpmutils.PROVIDENAME, rpmutils.PROVIDEVERSION, rpmutils.PROVIDEFLAGS), 142 Requires: getEntries(rpm.Header, rpmutils.REQUIRENAME, rpmutils.REQUIREVERSION, rpmutils.REQUIREFLAGS), 143 Conflicts: getEntries(rpm.Header, rpmutils.CONFLICTNAME, rpmutils.CONFLICTVERSION, rpmutils.CONFLICTFLAGS), 144 Obsoletes: getEntries(rpm.Header, rpmutils.OBSOLETENAME, rpmutils.OBSOLETEVERSION, rpmutils.OBSOLETEFLAGS), 145 Files: getFiles(rpm.Header), 146 Changelogs: getChangelogs(rpm.Header), 147 }, 148 } 149 150 if !validation.IsValidURL(p.VersionMetadata.ProjectURL) { 151 p.VersionMetadata.ProjectURL = "" 152 } 153 154 return p, nil 155 } 156 157 func getString(h *rpmutils.RpmHeader, tag int) string { 158 values, err := h.GetStrings(tag) 159 if err != nil || len(values) < 1 { 160 return "" 161 } 162 return values[0] 163 } 164 165 func getUInt64(h *rpmutils.RpmHeader, tag int) uint64 { 166 values, err := h.GetUint64s(tag) 167 if err != nil || len(values) < 1 { 168 return 0 169 } 170 return values[0] 171 } 172 173 func getEntries(h *rpmutils.RpmHeader, namesTag, versionsTag, flagsTag int) []*Entry { 174 names, err := h.GetStrings(namesTag) 175 if err != nil || len(names) == 0 { 176 return nil 177 } 178 flags, err := h.GetUint64s(flagsTag) 179 if err != nil || len(flags) == 0 { 180 return nil 181 } 182 versions, err := h.GetStrings(versionsTag) 183 if err != nil || len(versions) == 0 { 184 return nil 185 } 186 if len(names) != len(flags) || len(names) != len(versions) { 187 return nil 188 } 189 190 entries := make([]*Entry, 0, len(names)) 191 for i := range names { 192 e := &Entry{ 193 Name: names[i], 194 } 195 196 flags := flags[i] 197 if (flags&rpmutils.RPMSENSE_GREATER) != 0 && (flags&rpmutils.RPMSENSE_EQUAL) != 0 { 198 e.Flags = "GE" 199 } else if (flags&rpmutils.RPMSENSE_LESS) != 0 && (flags&rpmutils.RPMSENSE_EQUAL) != 0 { 200 e.Flags = "LE" 201 } else if (flags & rpmutils.RPMSENSE_GREATER) != 0 { 202 e.Flags = "GT" 203 } else if (flags & rpmutils.RPMSENSE_LESS) != 0 { 204 e.Flags = "LT" 205 } else if (flags & rpmutils.RPMSENSE_EQUAL) != 0 { 206 e.Flags = "EQ" 207 } 208 209 version := versions[i] 210 if version != "" { 211 parts := strings.Split(version, "-") 212 213 versionParts := strings.Split(parts[0], ":") 214 if len(versionParts) == 2 { 215 e.Version = versionParts[1] 216 e.Epoch = versionParts[0] 217 } else { 218 e.Version = versionParts[0] 219 e.Epoch = "0" 220 } 221 222 if len(parts) > 1 { 223 e.Release = parts[1] 224 } 225 } 226 227 entries = append(entries, e) 228 } 229 return entries 230 } 231 232 func getFiles(h *rpmutils.RpmHeader) []*File { 233 baseNames, _ := h.GetStrings(rpmutils.BASENAMES) 234 dirNames, _ := h.GetStrings(rpmutils.DIRNAMES) 235 dirIndexes, _ := h.GetUint32s(rpmutils.DIRINDEXES) 236 fileFlags, _ := h.GetUint32s(rpmutils.FILEFLAGS) 237 fileModes, _ := h.GetUint32s(rpmutils.FILEMODES) 238 239 files := make([]*File, 0, len(baseNames)) 240 for i := range baseNames { 241 if len(dirIndexes) <= i { 242 continue 243 } 244 dirIndex := dirIndexes[i] 245 if len(dirNames) <= int(dirIndex) { 246 continue 247 } 248 249 var fileType string 250 var isExecutable bool 251 if i < len(fileFlags) && (fileFlags[i]&rpmutils.RPMFILE_GHOST) != 0 { 252 fileType = "ghost" 253 } else if i < len(fileModes) { 254 if (fileModes[i] & sIFMT) == sIFDIR { 255 fileType = "dir" 256 } else { 257 mode := fileModes[i] & ^uint32(sIFMT) 258 isExecutable = (mode&sIXUSR) != 0 || (mode&sIXGRP) != 0 || (mode&sIXOTH) != 0 259 } 260 } 261 262 files = append(files, &File{ 263 Path: dirNames[dirIndex] + baseNames[i], 264 Type: fileType, 265 IsExecutable: isExecutable, 266 }) 267 } 268 269 return files 270 } 271 272 func getChangelogs(h *rpmutils.RpmHeader) []*Changelog { 273 texts, err := h.GetStrings(rpmutils.CHANGELOGTEXT) 274 if err != nil || len(texts) == 0 { 275 return nil 276 } 277 authors, err := h.GetStrings(rpmutils.CHANGELOGNAME) 278 if err != nil || len(authors) == 0 { 279 return nil 280 } 281 times, err := h.GetUint32s(rpmutils.CHANGELOGTIME) 282 if err != nil || len(times) == 0 { 283 return nil 284 } 285 if len(texts) != len(authors) || len(texts) != len(times) { 286 return nil 287 } 288 289 changelogs := make([]*Changelog, 0, len(texts)) 290 for i := range texts { 291 changelogs = append(changelogs, &Changelog{ 292 Author: authors[i], 293 Date: timeutil.TimeStamp(times[i]), 294 Text: texts[i], 295 }) 296 } 297 return changelogs 298 }