2023-06-20 20:08:48 +00:00
|
|
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
package arch
|
|
|
|
|
|
|
|
import (
|
2023-06-24 10:37:33 +00:00
|
|
|
"archive/tar"
|
2023-06-21 11:05:25 +00:00
|
|
|
"bytes"
|
2023-06-24 10:37:33 +00:00
|
|
|
"compress/gzip"
|
2023-06-20 20:08:48 +00:00
|
|
|
"crypto/md5"
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/hex"
|
|
|
|
"fmt"
|
2023-06-21 11:05:25 +00:00
|
|
|
"io"
|
2023-06-20 20:08:48 +00:00
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/mholt/archiver/v3"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Metadata for arch package.
|
|
|
|
type Metadata struct {
|
2023-07-02 20:03:34 +00:00
|
|
|
Filename string `json:"filename"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Base string `json:"base"`
|
|
|
|
Version string `json:"version"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
CompressedSize int64 `json:"compressed-size"`
|
|
|
|
InstalledSize int64 `json:"installed-size"`
|
|
|
|
MD5 string `json:"md5"`
|
|
|
|
SHA256 string `json:"sha256"`
|
|
|
|
URL string `json:"url"`
|
|
|
|
BuildDate int64 `json:"build-date"`
|
|
|
|
BaseDomain string `json:"base-domain"`
|
|
|
|
Packager string `json:"packager"`
|
2023-07-09 08:48:46 +00:00
|
|
|
Distribution []string `json:"distribution"`
|
2023-07-02 20:03:34 +00:00
|
|
|
Provides []string `json:"provides"`
|
|
|
|
License []string `json:"license"`
|
|
|
|
Arch []string `json:"arch"`
|
|
|
|
Depends []string `json:"depends"`
|
|
|
|
OptDepends []string `json:"opt-depends"`
|
|
|
|
MakeDepends []string `json:"make-depends"`
|
|
|
|
CheckDepends []string `json:"check-depends"`
|
|
|
|
Backup []string `json:"backup"`
|
2023-07-09 08:48:46 +00:00
|
|
|
// This list is created to ensure the consistency of pacman database file
|
|
|
|
// for specific combination of distribution and architecture.
|
|
|
|
DistroArch []string `json:"distro-arch"`
|
2023-06-20 20:08:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Function that recieves arch package archive data and returns it's metadata.
|
2023-07-02 20:03:34 +00:00
|
|
|
func EjectMetadata(filename, distribution, domain string, pkg []byte) (*Metadata, error) {
|
2023-06-26 09:28:14 +00:00
|
|
|
pkginfo, err := getPkginfo(pkg)
|
2023-06-20 20:08:48 +00:00
|
|
|
if err != nil {
|
2023-06-26 09:28:14 +00:00
|
|
|
return nil, err
|
2023-06-20 20:08:48 +00:00
|
|
|
}
|
2023-06-26 09:28:14 +00:00
|
|
|
var md = Metadata{
|
|
|
|
Filename: filename,
|
|
|
|
BaseDomain: domain,
|
|
|
|
CompressedSize: int64(len(pkg)),
|
|
|
|
MD5: md5sum(pkg),
|
|
|
|
SHA256: sha256sum(pkg),
|
2023-07-09 08:48:46 +00:00
|
|
|
Distribution: []string{distribution},
|
2023-06-20 20:08:48 +00:00
|
|
|
}
|
2023-06-26 09:28:14 +00:00
|
|
|
for _, line := range strings.Split(pkginfo, "\n") {
|
|
|
|
splt := strings.Split(line, " = ")
|
|
|
|
if len(splt) != 2 {
|
2023-06-20 20:08:48 +00:00
|
|
|
continue
|
|
|
|
}
|
2023-06-26 09:28:14 +00:00
|
|
|
switch splt[0] {
|
|
|
|
case "pkgname":
|
|
|
|
md.Name = splt[1]
|
|
|
|
case "pkgbase":
|
|
|
|
md.Base = splt[1]
|
|
|
|
case "pkgver":
|
|
|
|
md.Version = splt[1]
|
|
|
|
case "pkgdesc":
|
|
|
|
md.Description = splt[1]
|
|
|
|
case "url":
|
|
|
|
md.URL = splt[1]
|
|
|
|
case "packager":
|
|
|
|
md.Packager = splt[1]
|
|
|
|
case "builddate":
|
|
|
|
num, err := strconv.ParseInt(splt[1], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
md.BuildDate = num
|
|
|
|
case "size":
|
|
|
|
num, err := strconv.ParseInt(splt[1], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
md.InstalledSize = num
|
|
|
|
case "provides":
|
|
|
|
md.Provides = append(md.Provides, splt[1])
|
|
|
|
case "license":
|
|
|
|
md.License = append(md.License, splt[1])
|
|
|
|
case "arch":
|
|
|
|
md.Arch = append(md.Arch, splt[1])
|
2023-07-09 08:48:46 +00:00
|
|
|
md.DistroArch = append(md.DistroArch, distribution+"-"+splt[1])
|
2023-06-26 09:28:14 +00:00
|
|
|
case "depend":
|
|
|
|
md.Depends = append(md.Depends, splt[1])
|
|
|
|
case "optdepend":
|
2023-06-26 14:10:04 +00:00
|
|
|
md.OptDepends = append(md.OptDepends, splt[1])
|
2023-06-26 09:28:14 +00:00
|
|
|
case "makedepend":
|
2023-06-26 14:10:04 +00:00
|
|
|
md.MakeDepends = append(md.MakeDepends, splt[1])
|
2023-06-26 09:28:14 +00:00
|
|
|
case "checkdepend":
|
2023-06-26 14:10:04 +00:00
|
|
|
md.CheckDepends = append(md.CheckDepends, splt[1])
|
2023-06-26 09:28:14 +00:00
|
|
|
case "backup":
|
|
|
|
md.Depends = append(md.Backup, splt[1])
|
|
|
|
}
|
2023-06-20 20:08:48 +00:00
|
|
|
}
|
2023-06-26 09:28:14 +00:00
|
|
|
return &md, nil
|
2023-06-20 20:08:48 +00:00
|
|
|
}
|
|
|
|
|
2023-06-26 09:28:14 +00:00
|
|
|
func getPkginfo(data []byte) (string, error) {
|
2023-07-09 08:48:46 +00:00
|
|
|
pkgreader := bytes.NewReader(data)
|
2023-06-26 09:28:14 +00:00
|
|
|
zstd := archiver.NewTarZstd()
|
|
|
|
err := zstd.Open(pkgreader, int64(250000))
|
2023-06-20 20:08:48 +00:00
|
|
|
if err != nil {
|
2023-06-26 09:28:14 +00:00
|
|
|
return ``, err
|
2023-06-20 20:08:48 +00:00
|
|
|
}
|
2023-06-26 09:28:14 +00:00
|
|
|
for {
|
|
|
|
f, err := zstd.Read()
|
|
|
|
if err != nil {
|
|
|
|
return ``, err
|
|
|
|
}
|
|
|
|
if f.Name() != ".PKGINFO" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
b, err := io.ReadAll(f)
|
|
|
|
if err != nil {
|
|
|
|
return ``, err
|
|
|
|
}
|
|
|
|
return string(b), nil
|
2023-06-20 20:08:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func md5sum(data []byte) string {
|
|
|
|
sum := md5.Sum(data)
|
|
|
|
return hex.EncodeToString(sum[:])
|
|
|
|
}
|
|
|
|
|
|
|
|
func sha256sum(data []byte) string {
|
|
|
|
sum := sha256.Sum256(data)
|
|
|
|
return hex.EncodeToString(sum[:])
|
|
|
|
}
|
|
|
|
|
|
|
|
// This function returns pacman package description in unarchived raw database
|
|
|
|
// format.
|
|
|
|
func (m *Metadata) GetDbDesc() string {
|
|
|
|
return strings.Join(rmEmptyStrings([]string{
|
|
|
|
formatField("FILENAME", m.Filename),
|
|
|
|
formatField("NAME", m.Name),
|
|
|
|
formatField("BASE", m.Base),
|
|
|
|
formatField("VERSION", m.Version),
|
|
|
|
formatField("DESC", m.Description),
|
|
|
|
formatField("CSIZE", m.CompressedSize),
|
|
|
|
formatField("ISIZE", m.InstalledSize),
|
|
|
|
formatField("MD5SUM", m.MD5),
|
|
|
|
formatField("SHA256SUM", m.SHA256),
|
|
|
|
formatField("URL", m.URL),
|
|
|
|
formatField("LICENSE", m.License),
|
|
|
|
formatField("ARCH", m.Arch),
|
|
|
|
formatField("BUILDDATE", m.BuildDate),
|
|
|
|
formatField("PACKAGER", m.Packager),
|
|
|
|
formatField("PROVIDES", m.Provides),
|
|
|
|
formatField("DEPENDS", m.Depends),
|
|
|
|
formatField("OPTDEPENDS", m.OptDepends),
|
|
|
|
formatField("MAKEDEPENDS", m.MakeDepends),
|
|
|
|
formatField("CHECKDEPENDS", m.CheckDepends),
|
|
|
|
}), "\n\n") + "\n\n"
|
|
|
|
}
|
|
|
|
|
|
|
|
func formatField(field string, value any) string {
|
|
|
|
switch value := value.(type) {
|
|
|
|
case []string:
|
|
|
|
if value == nil {
|
|
|
|
return ``
|
|
|
|
}
|
|
|
|
val := strings.Join(value, "\n")
|
|
|
|
return fmt.Sprintf("%%%s%%\n%s", field, val)
|
|
|
|
case string:
|
|
|
|
return fmt.Sprintf("%%%s%%\n%s", field, value)
|
|
|
|
case int64:
|
|
|
|
return fmt.Sprintf("%%%s%%\n%d", field, value)
|
|
|
|
}
|
|
|
|
return ``
|
|
|
|
}
|
|
|
|
|
|
|
|
func rmEmptyStrings(s []string) []string {
|
|
|
|
var r []string
|
|
|
|
for _, str := range s {
|
|
|
|
if str != "" {
|
|
|
|
r = append(r, str)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2023-06-21 20:30:07 +00:00
|
|
|
// Join database or package names to prevent collisions with same packages in
|
|
|
|
// different user spaces. Skips empty strings and returns name joined with
|
|
|
|
// dots.
|
|
|
|
func Join(s ...string) string {
|
|
|
|
rez := ""
|
|
|
|
for i, v := range s {
|
|
|
|
if v == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if i+1 == len(s) {
|
|
|
|
rez += v
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
rez += v + "."
|
|
|
|
}
|
|
|
|
return rez
|
|
|
|
}
|
2023-06-24 10:37:33 +00:00
|
|
|
|
2023-07-02 20:03:34 +00:00
|
|
|
// Create pacman database archive based on provided package metadata structs.
|
|
|
|
func CreatePacmanDb(mds []*Metadata) ([]byte, error) {
|
|
|
|
entries := make(map[string][]byte)
|
2023-06-24 10:37:33 +00:00
|
|
|
|
2023-07-02 20:03:34 +00:00
|
|
|
for _, md := range mds {
|
|
|
|
entries[md.Name+"-"+md.Version+"/desc"] = []byte(md.GetDbDesc())
|
2023-06-25 20:05:02 +00:00
|
|
|
}
|
2023-06-25 10:15:29 +00:00
|
|
|
|
|
|
|
var out bytes.Buffer
|
|
|
|
|
|
|
|
// Write entries to new buffer and return it.
|
2023-07-02 20:03:34 +00:00
|
|
|
err := writeToArchive(entries, &out)
|
2023-06-25 10:15:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return out.Bytes(), nil
|
|
|
|
}
|
|
|
|
|
2023-07-02 20:03:34 +00:00
|
|
|
// Write pacman package entries to tarball.
|
2023-06-24 10:37:33 +00:00
|
|
|
func writeToArchive(files map[string][]byte, buf io.Writer) error {
|
|
|
|
gw := gzip.NewWriter(buf)
|
|
|
|
defer gw.Close()
|
|
|
|
tw := tar.NewWriter(gw)
|
|
|
|
defer tw.Close()
|
|
|
|
|
|
|
|
for name, content := range files {
|
|
|
|
hdr := &tar.Header{
|
|
|
|
Name: name,
|
|
|
|
Size: int64(len(content)),
|
|
|
|
Mode: int64(os.ModePerm),
|
|
|
|
}
|
|
|
|
|
|
|
|
err := tw.WriteHeader(hdr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = io.Copy(tw, bytes.NewReader(content))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2023-07-09 08:48:46 +00:00
|
|
|
|
|
|
|
// This function can be used to create a list containing unique values from 2
|
|
|
|
// passed arguements. The first is
|
|
|
|
func UnifiedList(first, second []string) []string {
|
|
|
|
unique := map[string]struct{}{}
|
|
|
|
for _, v := range first {
|
|
|
|
unique[v] = struct{}{}
|
|
|
|
}
|
|
|
|
for _, v := range second {
|
|
|
|
unique[v] = struct{}{}
|
|
|
|
}
|
|
|
|
var archs []string
|
|
|
|
for k := range unique {
|
|
|
|
archs = append(archs, k)
|
|
|
|
}
|
|
|
|
return archs
|
|
|
|
}
|