diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go index 97f6022d28..01505d5577 100644 --- a/models/packages/descriptor.go +++ b/models/packages/descriptor.go @@ -142,7 +142,7 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc case TypeAlpine: metadata = &alpine.VersionMetadata{} case TypeArch: - metadata = &arch.Metadata{} + metadata = &arch.VersionMetadata{} case TypeCargo: metadata = &cargo.Metadata{} case TypeChef: diff --git a/modules/packages/arch/metadata.go b/modules/packages/arch/metadata.go index 59d4b0c085..daef0af433 100644 --- a/modules/packages/arch/metadata.go +++ b/modules/packages/arch/metadata.go @@ -6,194 +6,274 @@ package arch import ( "archive/tar" "bufio" + "bytes" "compress/gzip" "encoding/hex" + "errors" "fmt" "io" "os" + "regexp" "strconv" "strings" - packages_module "code.gitea.io/gitea/modules/packages" - + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/validation" "github.com/mholt/archiver/v3" ) -// JSON with pacakage parameters that are not related to specific -// architecture/distribution that will be stored in sql database. -type Metadata struct { - URL string `json:"url"` +type Package struct { + Name string `json:"name"` + Version string `json:"version"` + VersionMetadata VersionMetadata + FileMetadata FileMetadata +} + +// Arch package metadata related to specific version. +// Version metadata the same across different architectures and distributions. +type VersionMetadata struct { + Base string `json:"base"` Description string `json:"description"` + ProjectURL string `json:"project_url"` + Groups []string `json:"groups,omitempty"` Provides []string `json:"provides,omitempty"` License []string `json:"license,omitempty"` Depends []string `json:"depends,omitempty"` OptDepends []string `json:"opt_depends,omitempty"` MakeDepends []string `json:"make_depends,omitempty"` CheckDepends []string `json:"check_depends,omitempty"` + Backup []string `json:"backup,omitempty"` } -// Package description file that will be saved as .desc file in object storage. -// This file will be used to create pacman database. -type DbDesc struct { - 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"` - ProjectURL string `json:"project_url"` - BuildDate int64 `json:"build_date"` - Packager string `json:"packager"` - Provides []string `json:"provides,omitempty"` - License []string `json:"license,omitempty"` - Arch []string `json:"arch,omitempty"` - Depends []string `json:"depends,omitempty"` - OptDepends []string `json:"opt_depends,omitempty"` - MakeDepends []string `json:"make_depends,omitempty"` - CheckDepends []string `json:"check_depends,omitempty"` - Backup []string `json:"backup,omitempty"` +// Metadata related to specific pakcage file. +// This metadata might vary for different architecture and distribution. +type FileMetadata struct { + CompressedSize int64 `json:"compressed_size"` + InstalledSize int64 `json:"installed_size"` + MD5 string `json:"md5"` + SHA256 string `json:"sha256"` + BuildDate int64 `json:"build_date"` + Packager string `json:"packager"` + Arch string `json:"arch"` } // Function that receives arch package archive data and returns it's metadata. -func ParseMetadata(file, distro string, b *packages_module.HashedBuffer) (*DbDesc, error) { - pkginfo, err := getPkginfo(b) +func ParsePackage(r io.Reader, md5, sha256 []byte, size int64) (*Package, error) { + zstd := archiver.NewTarZstd() + err := zstd.Open(r, 0) if err != nil { return nil, err } + defer zstd.Close() - // Add package blob parameters to arch related desc. - hashMD5, _, hashSHA256, _ := b.Sums() - md := DbDesc{ - Filename: file, - Name: file, - CompressedSize: b.Size(), - MD5: hex.EncodeToString(hashMD5), - SHA256: hex.EncodeToString(hashSHA256), - } + var pkg *Package + var mtree bool - for _, line := range strings.Split(pkginfo, "\n") { - splt := strings.Split(line, " = ") - if len(splt) != 2 { - continue - } - var ( - parameter = splt[0] - value = splt[1] - ) - - switch parameter { - case "pkgname": - md.Name = value - case "pkgbase": - md.Base = value - case "pkgver": - md.Version = value - case "pkgdesc": - md.Description = value - case "url": - md.ProjectURL = value - case "packager": - md.Packager = value - case "provides": - md.Provides = append(md.Provides, value) - case "license": - md.License = append(md.License, value) - case "arch": - md.Arch = append(md.Arch, value) - case "depend": - md.Depends = append(md.Depends, value) - case "optdepend": - md.OptDepends = append(md.OptDepends, value) - case "makedepend": - md.MakeDepends = append(md.MakeDepends, value) - case "checkdepend": - md.CheckDepends = append(md.CheckDepends, value) - case "backup": - md.Backup = append(md.Backup, value) - case "builddate": - md.BuildDate, err = strconv.ParseInt(value, 10, 64) - if err != nil { - return nil, err - } - case "size": - md.InstalledSize, err = strconv.ParseInt(value, 10, 64) - if err != nil { - return nil, err - } - } - } - - return &md, nil -} - -// Eject .PKGINFO file as string from package archive. -func getPkginfo(data io.Reader) (string, error) { - br := bufio.NewReader(data) - zstd := archiver.NewTarZstd() - err := zstd.Open(br, int64(250000)) - if err != nil { - return ``, err - } for { f, err := zstd.Read() - if err != nil { - return ``, err + if err == io.EOF { + break } - if f.Name() != ".PKGINFO" { + if err != nil { + return nil, err + } + defer f.Close() + + switch f.Name() { + case ".PKGINFO": + pkg, err = ParsePackageInfo(f) + if err != nil { + return nil, err + } + case ".MTREE": + mtree = true + } + } + + if pkg == nil { + return nil, util.NewInvalidArgumentErrorf(".PKGINFO file not found") + } + + if !mtree { + return nil, util.NewInvalidArgumentErrorf(".MTREE file not found") + } + + pkg.FileMetadata.CompressedSize = size + pkg.FileMetadata.SHA256 = hex.EncodeToString(sha256) + pkg.FileMetadata.MD5 = hex.EncodeToString(md5) + + return pkg, nil +} + +// Function that accepts reader for .PKGINFO file from package archive, +// validates all field according to PKGBUILD spec and returns package. +func ParsePackageInfo(r io.Reader) (*Package, error) { + p := &Package{} + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + + if strings.HasPrefix(line, "#") { continue } - b, err := io.ReadAll(f) - if err != nil { - return ``, err + + i := strings.IndexRune(line, '=') + if i == -1 { + continue + } + + key := strings.TrimSpace(line[:i]) + value := strings.TrimSpace(line[i+1:]) + + switch key { + case "pkgname": + p.Name = value + case "pkgbase": + p.VersionMetadata.Base = value + case "pkgver": + p.Version = value + case "pkgdesc": + p.VersionMetadata.Description = value + case "url": + p.VersionMetadata.ProjectURL = value + case "packager": + p.FileMetadata.Packager = value + case "arch": + p.FileMetadata.Arch = value + case "provides": + p.VersionMetadata.Provides = append(p.VersionMetadata.Provides, value) + case "license": + p.VersionMetadata.License = append(p.VersionMetadata.License, value) + case "depend": + p.VersionMetadata.Depends = append(p.VersionMetadata.Depends, value) + case "optdepend": + p.VersionMetadata.OptDepends = append(p.VersionMetadata.OptDepends, value) + case "makedepend": + p.VersionMetadata.MakeDepends = append(p.VersionMetadata.MakeDepends, value) + case "checkdepend": + p.VersionMetadata.CheckDepends = append(p.VersionMetadata.CheckDepends, value) + case "backup": + p.VersionMetadata.Backup = append(p.VersionMetadata.Backup, value) + case "group": + p.VersionMetadata.Groups = append(p.VersionMetadata.Groups, value) + case "builddate": + bd, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return nil, err + } + p.FileMetadata.BuildDate = bd + case "size": + is, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return nil, err + } + p.FileMetadata.InstalledSize = is } - return string(b), nil } + + return p, errors.Join(scanner.Err(), ValidatePackageSpec(p)) +} + +// Arch package validation according to PKGBUILD specification: +// https://man.archlinux.org/man/PKGBUILD.5 +func ValidatePackageSpec(p *Package) error { + var ( + reName = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+$`) + reVer = regexp.MustCompile(`^[a-zA-Z0-9:_.+]+-+[0-9]+$`) + reOptDep = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+$|^[a-zA-Z0-9@._+-]+(:.*)`) + rePkgVer = regexp.MustCompile(`^[a-zA-Z0-9@._+-]+$|^[a-zA-Z0-9@._+-]+(>.*)|^[a-zA-Z0-9@._+-]+(<.*)|^[a-zA-Z0-9@._+-]+(=.*)`) + ) + + if !reName.MatchString(p.Name) { + return util.NewInvalidArgumentErrorf("invalid package name") + } + if !reName.MatchString(p.VersionMetadata.Base) { + return util.NewInvalidArgumentErrorf("invalid package base") + } + if !reVer.MatchString(p.Version) { + return util.NewInvalidArgumentErrorf("invalid package version") + } + if p.FileMetadata.Arch == "" { + return util.NewInvalidArgumentErrorf("architecture should be specified") + } + if p.VersionMetadata.ProjectURL != "" { + if !validation.IsValidURL(p.VersionMetadata.ProjectURL) { + return util.NewInvalidArgumentErrorf("invalid project URL") + } + } + for _, cd := range p.VersionMetadata.CheckDepends { + if !rePkgVer.MatchString(cd) { + return util.NewInvalidArgumentErrorf("invalid check dependency: " + cd) + } + } + for _, d := range p.VersionMetadata.Depends { + if !rePkgVer.MatchString(d) { + return util.NewInvalidArgumentErrorf("invalid dependency: " + d) + } + } + for _, md := range p.VersionMetadata.MakeDepends { + if !rePkgVer.MatchString(md) { + return util.NewInvalidArgumentErrorf("invalid make dependency: " + md) + } + } + for _, p := range p.VersionMetadata.Provides { + if !rePkgVer.MatchString(p) { + return util.NewInvalidArgumentErrorf("invalid provides: " + p) + } + } + for _, od := range p.VersionMetadata.OptDepends { + if !reOptDep.MatchString(od) { + return util.NewInvalidArgumentErrorf("invalid optional dependency: " + od) + } + } + for _, bf := range p.VersionMetadata.Backup { + if strings.HasPrefix(bf, "/") { + return util.NewInvalidArgumentErrorf("backup file contains leading forward slash") + } + } + return nil } // Create pacman package description file. -func (m *DbDesc) String() string { - entries := []struct{ key, value string }{ - {"FILENAME", m.Filename}, - {"NAME", m.Name}, - {"BASE", m.Base}, - {"VERSION", m.Version}, - {"DESC", m.Description}, - {"CSIZE", fmt.Sprintf("%d", m.CompressedSize)}, - {"ISIZE", fmt.Sprintf("%d", m.InstalledSize)}, - {"MD5SUM", m.MD5}, - {"SHA256SUM", m.SHA256}, - {"URL", m.ProjectURL}, - {"LICENSE", strings.Join(m.License, "\n")}, - {"ARCH", strings.Join(m.Arch, "\n")}, - {"BUILDDATE", fmt.Sprintf("%d", m.BuildDate)}, - {"PACKAGER", m.Packager}, - {"PROVIDES", strings.Join(m.Provides, "\n")}, - {"DEPENDS", strings.Join(m.Depends, "\n")}, - {"OPTDEPENDS", strings.Join(m.OptDepends, "\n")}, - {"MAKEDEPENDS", strings.Join(m.MakeDepends, "\n")}, - {"CHECKDEPENDS", strings.Join(m.CheckDepends, "\n")}, +func (p *Package) Desc() string { + entries := [40]string{ + "FILENAME", fmt.Sprintf("%s-%s-%s.pkg.tar.zst", p.Name, p.Version, p.FileMetadata.Arch), + "NAME", p.Name, + "BASE", p.VersionMetadata.Base, + "VERSION", p.Version, + "DESC", p.VersionMetadata.Description, + "GROUPS", strings.Join(p.VersionMetadata.Groups, "\n"), + "CSIZE", fmt.Sprintf("%d", p.FileMetadata.CompressedSize), + "ISIZE", fmt.Sprintf("%d", p.FileMetadata.InstalledSize), + "MD5SUM", p.FileMetadata.MD5, + "SHA256SUM", p.FileMetadata.SHA256, + "URL", p.VersionMetadata.ProjectURL, + "LICENSE", strings.Join(p.VersionMetadata.License, "\n"), + "ARCH", p.FileMetadata.Arch, + "BUILDDATE", fmt.Sprintf("%d", p.FileMetadata.BuildDate), + "PACKAGER", p.FileMetadata.Packager, + "PROVIDES", strings.Join(p.VersionMetadata.Provides, "\n"), + "DEPENDS", strings.Join(p.VersionMetadata.Depends, "\n"), + "OPTDEPENDS", strings.Join(p.VersionMetadata.OptDepends, "\n"), + "MAKEDEPENDS", strings.Join(p.VersionMetadata.MakeDepends, "\n"), + "CHECKDEPENDS", strings.Join(p.VersionMetadata.CheckDepends, "\n"), } var result string - for _, e := range entries { - if e.value != "" { - result += fmt.Sprintf("%%%s%%\n%s\n\n", e.key, e.value) + for i := 0; i < 40; i += 2 { + if entries[i+1] != "" { + result += fmt.Sprintf("%%%s%%\n%s\n\n", entries[i], entries[i+1]) } } return result } // Create pacman database archive based on provided package metadata structs. -func CreatePacmanDb(entries map[string][]byte) (io.ReadSeeker, error) { - out, err := packages_module.NewHashedBuffer() - if err != nil { - return nil, err - } +func CreatePacmanDb(entries map[string][]byte) (*bytes.Buffer, error) { + var b bytes.Buffer - gw := gzip.NewWriter(out) + gw := gzip.NewWriter(&b) tw := tar.NewWriter(gw) for name, content := range entries { @@ -204,20 +284,13 @@ func CreatePacmanDb(entries map[string][]byte) (io.ReadSeeker, error) { } if err := tw.WriteHeader(header); err != nil { - tw.Close() - gw.Close() - return nil, err + return nil, errors.Join(err, tw.Close(), gw.Close()) } if _, err := tw.Write(content); err != nil { - tw.Close() - gw.Close() - return nil, err + return nil, errors.Join(err, tw.Close(), gw.Close()) } } - tw.Close() - gw.Close() - - return out, nil + return &b, errors.Join(tw.Close(), gw.Close()) } diff --git a/modules/packages/arch/metadata_test.go b/modules/packages/arch/metadata_test.go index fb5897dd9c..b08c4b4163 100644 --- a/modules/packages/arch/metadata_test.go +++ b/modules/packages/arch/metadata_test.go @@ -4,44 +4,337 @@ package arch import ( + "bytes" "encoding/base64" + "errors" "io" "os" + "strings" "testing" "testing/fstest" "time" - packages_module "code.gitea.io/gitea/modules/packages" - "github.com/mholt/archiver/v3" "github.com/stretchr/testify/assert" ) -const pkginfo = `# Generated by makepkg 6.0.2 +func TestParsePackage(t *testing.T) { + // Minimal PKGINFO contents and test FS + const PKGINFO = `pkgname = a +pkgbase = b +pkgver = 1-2 +arch = x86_64 +` + fs := fstest.MapFS{ + "pkginfo": &fstest.MapFile{ + Data: []byte(PKGINFO), + Mode: os.ModePerm, + ModTime: time.Now(), + }, + "mtree": &fstest.MapFile{ + Data: []byte("data"), + Mode: os.ModePerm, + ModTime: time.Now(), + }, + } + + // Test .PKGINFO file + pinf, err := fs.Stat("pkginfo") + assert.NoError(t, err) + + pfile, err := fs.Open("pkginfo") + assert.NoError(t, err) + + parcname, err := archiver.NameInArchive(pinf, ".PKGINFO", ".PKGINFO") + assert.NoError(t, err) + + // Test .MTREE file + minf, err := fs.Stat("mtree") + assert.NoError(t, err) + + mfile, err := fs.Open("mtree") + assert.NoError(t, err) + + marcname, err := archiver.NameInArchive(minf, ".MTREE", ".MTREE") + assert.NoError(t, err) + + t.Run("normal archive", func(t *testing.T) { + var buf bytes.Buffer + + archive := archiver.NewTarZstd() + archive.Create(&buf) + + err = archive.Write(archiver.File{ + FileInfo: archiver.FileInfo{ + FileInfo: pinf, + CustomName: parcname, + }, + ReadCloser: pfile, + }) + assert.NoError(t, errors.Join(pfile.Close(), err)) + + err = archive.Write(archiver.File{ + FileInfo: archiver.FileInfo{ + FileInfo: minf, + CustomName: marcname, + }, + ReadCloser: mfile, + }) + assert.NoError(t, errors.Join(mfile.Close(), archive.Close(), err)) + + _, err = ParsePackage(&buf, []byte{}, []byte{}, 0) + + assert.NoError(t, err) + }) + + t.Run("missing .PKGINFO", func(t *testing.T) { + var buf bytes.Buffer + + archive := archiver.NewTarZstd() + archive.Create(&buf) + + assert.NoError(t, archive.Close()) + + _, err = ParsePackage(&buf, []byte{}, []byte{}, 0) + + assert.Error(t, err) + assert.Contains(t, err.Error(), ".PKGINFO file not found") + }) + + t.Run("missing .MTREE", func(t *testing.T) { + var buf bytes.Buffer + + pfile, err := fs.Open("pkginfo") + assert.NoError(t, err) + + archive := archiver.NewTarZstd() + archive.Create(&buf) + + err = archive.Write(archiver.File{ + FileInfo: archiver.FileInfo{ + FileInfo: pinf, + CustomName: parcname, + }, + ReadCloser: pfile, + }) + assert.NoError(t, errors.Join(pfile.Close(), archive.Close(), err)) + + _, err = ParsePackage(&buf, []byte{}, []byte{}, 0) + + assert.Error(t, err) + assert.Contains(t, err.Error(), ".MTREE file not found") + }) +} + +func TestParsePackageInfo(t *testing.T) { + const PKGINFO = `# Generated by makepkg 6.0.2 # using fakeroot version 1.31 -pkgname = zstd -pkgbase = zstd -pkgver = 1.5.5-1 -pkgdesc = Zstandard - Fast real-time compression algorithm -url = https://facebook.github.io/zstd/ -builddate = 1681646714 -packager = Jelle van der Waa -size = 1500453 +pkgname = a +pkgbase = b +pkgver = 1-2 +pkgdesc = comment +url = https://example.com/ +group = group +builddate = 3 +packager = Name Surname +size = 5 arch = x86_64 license = BSD -license = GPL2 -provides = libzstd.so=1-64 -depend = glibc -depend = gcc-libs -depend = zlib -depend = xz -depend = lz4 +provides = pvd +depend = smth +optdepend = hex +checkdepend = ola makedepend = cmake -makedepend = gtest -makedepend = ninja +backup = usr/bin/paket1 ` + p, err := ParsePackageInfo(strings.NewReader(PKGINFO)) + assert.NoError(t, err) + assert.Equal(t, Package{ + Name: "a", + Version: "1-2", + VersionMetadata: VersionMetadata{ + Base: "b", + Description: "comment", + ProjectURL: "https://example.com/", + Groups: []string{"group"}, + Provides: []string{"pvd"}, + License: []string{"BSD"}, + Depends: []string{"smth"}, + OptDepends: []string{"hex"}, + MakeDepends: []string{"cmake"}, + CheckDepends: []string{"ola"}, + Backup: []string{"usr/bin/paket1"}, + }, + FileMetadata: FileMetadata{ + InstalledSize: 5, + BuildDate: 3, + Packager: "Name Surname ", + Arch: "x86_64", + }, + }, *p) +} -const pkgdesc = `%FILENAME% +func TestValidatePackageSpec(t *testing.T) { + var newpkg = func() Package { + return Package{ + Name: "abc", + Version: "1-1", + VersionMetadata: VersionMetadata{ + Base: "ghx", + Description: "whoami", + ProjectURL: "https://example.com/", + Groups: []string{"gnome"}, + Provides: []string{"abc", "def"}, + License: []string{"GPL"}, + Depends: []string{"go", "gpg=1", "curl>=3", "git<=7"}, + OptDepends: []string{"git: something", "make"}, + MakeDepends: []string{"chrom"}, + CheckDepends: []string{"bariy"}, + Backup: []string{"etc/pacman.d/filo"}, + }, + FileMetadata: FileMetadata{ + CompressedSize: 1, + InstalledSize: 2, + MD5: "abc", + SHA256: "def", + BuildDate: 3, + Packager: "smon", + Arch: "x86_64", + }, + } + } + + t.Run("valid package", func(t *testing.T) { + p := newpkg() + + err := ValidatePackageSpec(&p) + + assert.NoError(t, err) + }) + + t.Run("invalid package name", func(t *testing.T) { + p := newpkg() + p.Name = "!$%@^!*&()" + + err := ValidatePackageSpec(&p) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid package name") + }) + + t.Run("invalid package base", func(t *testing.T) { + p := newpkg() + p.VersionMetadata.Base = "!$%@^!*&()" + + err := ValidatePackageSpec(&p) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid package base") + }) + + t.Run("invalid package version", func(t *testing.T) { + p := newpkg() + p.VersionMetadata.Base = "una-luna?" + + err := ValidatePackageSpec(&p) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid package base") + }) + + t.Run("invalid package version", func(t *testing.T) { + p := newpkg() + p.Version = "una-luna" + + err := ValidatePackageSpec(&p) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid package version") + }) + + t.Run("missing architecture", func(t *testing.T) { + p := newpkg() + p.FileMetadata.Arch = "" + + err := ValidatePackageSpec(&p) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "architecture should be specified") + }) + + t.Run("invalid URL", func(t *testing.T) { + p := newpkg() + p.VersionMetadata.ProjectURL = "http%%$#" + + err := ValidatePackageSpec(&p) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid project URL") + }) + + t.Run("invalid check dependency", func(t *testing.T) { + p := newpkg() + p.VersionMetadata.CheckDepends = []string{"Err^_^"} + + err := ValidatePackageSpec(&p) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid check dependency") + }) + + t.Run("invalid dependency", func(t *testing.T) { + p := newpkg() + p.VersionMetadata.Depends = []string{"^^abc"} + + err := ValidatePackageSpec(&p) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid dependency") + }) + + t.Run("invalid make dependency", func(t *testing.T) { + p := newpkg() + p.VersionMetadata.MakeDepends = []string{"^m^"} + + err := ValidatePackageSpec(&p) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid make dependency") + }) + + t.Run("invalid provides", func(t *testing.T) { + p := newpkg() + p.VersionMetadata.Provides = []string{"^m^"} + + err := ValidatePackageSpec(&p) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid provides") + }) + + t.Run("invalid optional dependency", func(t *testing.T) { + p := newpkg() + p.VersionMetadata.OptDepends = []string{"^m^:MM"} + + err := ValidatePackageSpec(&p) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid optional dependency") + }) + + t.Run("invalid optional dependency", func(t *testing.T) { + p := newpkg() + p.VersionMetadata.Backup = []string{"/ola/cola"} + + err := ValidatePackageSpec(&p) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "backup file contains leading forward slash") + }) +} + +func TestDescString(t *testing.T) { + const pkgdesc = `%FILENAME% zstd-1.5.5-1-x86_64.pkg.tar.zst %NAME% @@ -56,6 +349,10 @@ zstd %DESC% Zstandard - Fast real-time compression algorithm +%GROUPS% +dummy1 +dummy2 + %CSIZE% 401 @@ -94,93 +391,52 @@ zlib xz lz4 +%OPTDEPENDS% +dummy3 +dummy4 + %MAKEDEPENDS% cmake gtest ninja +%CHECKDEPENDS% +dummy5 +dummy6 + ` -const dbarchive = "H4sIAAAAAAAA/0rLzEnVS60oYaAhMDAwMDA3NwfTBgYG6LSBgYEpEtuAwcDQwMzUgEHBgJaOgoHS4pLEIgYDiu1C99wQASmlubmVA+2IUTAKRsEoGAV0B4AAAAD//2VF3KIACAAA" - -func TestMetadata(t *testing.T) { - fs := fstest.MapFS{ - "pkginfo": &fstest.MapFile{ - Data: []byte(pkginfo), - Mode: os.ModePerm, - ModTime: time.Now(), + md := &Package{ + Name: "zstd", + Version: "1.5.5-1", + VersionMetadata: VersionMetadata{ + Base: "zstd", + Description: "Zstandard - Fast real-time compression algorithm", + ProjectURL: "https://facebook.github.io/zstd/", + Groups: []string{"dummy1", "dummy2"}, + Provides: []string{"libzstd.so=1-64"}, + License: []string{"BSD", "GPL2"}, + Depends: []string{"glibc", "gcc-libs", "zlib", "xz", "lz4"}, + OptDepends: []string{"dummy3", "dummy4"}, + MakeDepends: []string{"cmake", "gtest", "ninja"}, + CheckDepends: []string{"dummy5", "dummy6"}, + }, + FileMetadata: FileMetadata{ + CompressedSize: 401, + InstalledSize: 1500453, + MD5: "5016660ef3d9aa148a7b72a08d3df1b2", + SHA256: "9fa4ede47e35f5971e4f26ecadcbfb66ab79f1d638317ac80334a3362dedbabd", + BuildDate: 1681646714, + Packager: "Jelle van der Waa ", + Arch: "x86_64", }, } - - info, err := fs.Stat("pkginfo") - assert.NoError(t, err) - - file, err := fs.Open("pkginfo") - assert.NoError(t, err) - - buf, err := packages_module.NewHashedBuffer() - assert.NoError(t, err) - - archive := archiver.NewTarZstd() - archive.Create(buf) - - n, err := archiver.NameInArchive(info, ".PKGINFO", ".PKGINFO") - assert.NoError(t, err) - - err = archive.Write(archiver.File{ - FileInfo: archiver.FileInfo{ - FileInfo: info, - CustomName: n, - }, - ReadCloser: file, - }) - file.Close() - archive.Close() - assert.NoError(t, err) - - md, err := ParseMetadata("zstd-1.5.5-1-x86_64.pkg.tar.zst", "archlinux", buf) - assert.NoError(t, err) - - assert.Equal(t, md.Name, "zstd") - assert.Equal(t, md.Base, "zstd") - assert.Equal(t, md.Version, "1.5.5-1") - assert.Equal(t, md.Description, "Zstandard - Fast real-time compression algorithm") - assert.Equal(t, md.ProjectURL, "https://facebook.github.io/zstd/") - assert.Equal(t, md.BuildDate, int64(1681646714)) - assert.Equal(t, md.Packager, "Jelle van der Waa ") - assert.Equal(t, md.InstalledSize, int64(1500453)) - assert.Equal(t, md.Arch, []string{"x86_64"}) - assert.Equal(t, md.License, []string{"BSD", "GPL2"}) - assert.Equal(t, md.Provides, []string{"libzstd.so=1-64"}) - assert.Equal(t, md.Depends, []string{"glibc", "gcc-libs", "zlib", "xz", "lz4"}) - assert.Equal(t, md.MakeDepends, []string{"cmake", "gtest", "ninja"}) + assert.Equal(t, pkgdesc, md.Desc()) } -func TestDescString(t *testing.T) { - md := &DbDesc{ - Filename: "zstd-1.5.5-1-x86_64.pkg.tar.zst", - Name: "zstd", - Base: "zstd", - Version: "1.5.5-1", - Description: "Zstandard - Fast real-time compression algorithm", - CompressedSize: 401, - InstalledSize: 1500453, - MD5: "5016660ef3d9aa148a7b72a08d3df1b2", - SHA256: "9fa4ede47e35f5971e4f26ecadcbfb66ab79f1d638317ac80334a3362dedbabd", - ProjectURL: "https://facebook.github.io/zstd/", - BuildDate: 1681646714, - Packager: "Jelle van der Waa ", - Provides: []string{"libzstd.so=1-64"}, - License: []string{"BSD", "GPL2"}, - Arch: []string{"x86_64"}, - Depends: []string{"glibc", "gcc-libs", "zlib", "xz", "lz4"}, - MakeDepends: []string{"cmake", "gtest", "ninja"}, - } - desc := md.String() - assert.Equal(t, pkgdesc, desc) -} +func TestCreatePacmanDb(t *testing.T) { + const dbarchive = "H4sIAAAAAAAA/0rLzEnVS60oYaAhMDAwMDA3NwfTBgYG6LSBgYEpEtuAwcDQwMzUgEHBgJaOgoHS4pLEIgYDiu1C99wQASmlubmVA+2IUTAKRsEoGAV0B4AAAAD//2VF3KIACAAA" -func TestDatabase(t *testing.T) { db, err := CreatePacmanDb(map[string][]byte{ "file.ext": []byte("dummy"), }) diff --git a/routers/api/packages/arch/arch.go b/routers/api/packages/arch/arch.go index c52ce2f88b..358ca81c6e 100644 --- a/routers/api/packages/arch/arch.go +++ b/routers/api/packages/arch/arch.go @@ -4,6 +4,7 @@ package arch import ( + "bytes" "net/http" "strings" @@ -95,7 +96,7 @@ func Get(ctx *context.Context) { return } - ctx.ServeContent(db, &context.ServeHeaderOptions{ + ctx.ServeContent(bytes.NewReader(db.Bytes()), &context.ServeHeaderOptions{ Filename: file, }) return diff --git a/services/packages/arch/service.go b/services/packages/arch/service.go index c78689212c..aa11a73048 100644 --- a/services/packages/arch/service.go +++ b/services/packages/arch/service.go @@ -83,7 +83,7 @@ func getPackageFile(ctx *context.Context, distro, file string) (*packages_model. // requested combination of architecture and distribution. When/If the first // compatible version is found, related desc file will be loaded from package // properties and added to resulting .db.tar.gz archive. -func CreatePacmanDb(ctx *context.Context, owner, arch, distro string) (io.ReadSeeker, error) { +func CreatePacmanDb(ctx *context.Context, owner, arch, distro string) (*bytes.Buffer, error) { pkgs, err := packages_model.GetPackagesByType(ctx, ctx.Package.Owner.ID, packages_model.TypeArch) if err != nil { return nil, err diff --git a/services/packages/arch/upload.go b/services/packages/arch/upload.go index 1bb38e967c..654bbebf9b 100644 --- a/services/packages/arch/upload.go +++ b/services/packages/arch/upload.go @@ -24,7 +24,9 @@ func UploadArchPackage(ctx *context.Context, upload io.Reader, filename, distro, } defer buf.Close() - desc, err := arch_module.ParseMetadata(filename, distro, buf) + md5, _, sha256, _ := buf.Sums() + + p, err := arch_module.ParsePackage(buf, md5, sha256, buf.Size()) if err != nil { return false, nil, err } @@ -35,7 +37,7 @@ func UploadArchPackage(ctx *context.Context, upload io.Reader, filename, distro, } properties := map[string]string{ - "desc": desc.String(), + "desc": p.Desc(), } if sign != "" { _, err := hex.DecodeString(sign) @@ -50,20 +52,11 @@ func UploadArchPackage(ctx *context.Context, upload io.Reader, filename, distro, PackageInfo: packages_service.PackageInfo{ Owner: ctx.Package.Owner, PackageType: packages_model.TypeArch, - Name: desc.Name, - Version: desc.Version, - }, - Creator: ctx.Doer, - Metadata: &arch_module.Metadata{ - URL: desc.ProjectURL, - Description: desc.Description, - Provides: desc.Provides, - License: desc.License, - Depends: desc.Depends, - OptDepends: desc.OptDepends, - MakeDepends: desc.MakeDepends, - CheckDepends: desc.CheckDepends, + Name: p.Name, + Version: p.Version, }, + Creator: ctx.Doer, + Metadata: p.VersionMetadata, }, &packages_service.PackageFileCreationInfo{ PackageFileInfo: packages_service.PackageFileInfo{