// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package arch import ( "bytes" "errors" "os" "strings" "testing" "testing/fstest" "time" "code.gitea.io/gitea/modules/packages" "github.com/mholt/archiver/v3" "github.com/stretchr/testify/require" ) 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") require.NoError(t, err) pfile, err := fs.Open("pkginfo") require.NoError(t, err) parcname, err := archiver.NameInArchive(pinf, ".PKGINFO", ".PKGINFO") require.NoError(t, err) // Test .MTREE file minf, err := fs.Stat("mtree") require.NoError(t, err) mfile, err := fs.Open("mtree") require.NoError(t, err) marcname, err := archiver.NameInArchive(minf, ".MTREE", ".MTREE") require.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, }) require.NoError(t, errors.Join(pfile.Close(), err)) err = archive.Write(archiver.File{ FileInfo: archiver.FileInfo{ FileInfo: minf, CustomName: marcname, }, ReadCloser: mfile, }) require.NoError(t, errors.Join(mfile.Close(), archive.Close(), err)) reader, err := packages.CreateHashedBufferFromReader(&buf) if err != nil { t.Fatal(err) } defer reader.Close() _, err = ParsePackage(reader) require.NoError(t, err) }) t.Run("missing .PKGINFO", func(t *testing.T) { var buf bytes.Buffer archive := archiver.NewTarZstd() archive.Create(&buf) require.NoError(t, archive.Close()) reader, err := packages.CreateHashedBufferFromReader(&buf) require.NoError(t, err) defer reader.Close() _, err = ParsePackage(reader) require.Error(t, err) require.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") require.NoError(t, err) archive := archiver.NewTarZstd() archive.Create(&buf) err = archive.Write(archiver.File{ FileInfo: archiver.FileInfo{ FileInfo: pinf, CustomName: parcname, }, ReadCloser: pfile, }) require.NoError(t, errors.Join(pfile.Close(), archive.Close(), err)) reader, err := packages.CreateHashedBufferFromReader(&buf) require.NoError(t, err) defer reader.Close() _, err = ParsePackage(reader) require.Error(t, err) require.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 = 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 provides = pvd depend = smth optdepend = hex checkdepend = ola makedepend = cmake backup = usr/bin/paket1 ` p, err := ParsePackageInfo("zst", strings.NewReader(PKGINFO)) require.NoError(t, err) require.Equal(t, Package{ CompressType: "zst", 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) } func TestValidatePackageSpec(t *testing.T) { 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", "libgcc=1.0", "gzip>1.0", "gz>=1.0", "lz<1.0", "gzip<=1.0", "zstd>1.0:foo bar"}, MakeDepends: []string{"chrom"}, CheckDepends: []string{"bariy"}, Backup: []string{"etc/pacman.d/filo"}, }, FileMetadata: FileMetadata{ CompressedSize: 1, InstalledSize: 2, SHA256: "def", BuildDate: 3, Packager: "smon", Arch: "x86_64", }, } } t.Run("valid package", func(t *testing.T) { p := newpkg() err := ValidatePackageSpec(&p) require.NoError(t, err) }) t.Run("invalid package name", func(t *testing.T) { p := newpkg() p.Name = "!$%@^!*&()" err := ValidatePackageSpec(&p) require.Error(t, err) require.Contains(t, err.Error(), "invalid package name") }) t.Run("invalid package base", func(t *testing.T) { p := newpkg() p.VersionMetadata.Base = "!$%@^!*&()" err := ValidatePackageSpec(&p) require.Error(t, err) require.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) require.Error(t, err) require.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) require.Error(t, err) require.Contains(t, err.Error(), "invalid package version") }) t.Run("missing architecture", func(t *testing.T) { p := newpkg() p.FileMetadata.Arch = "" err := ValidatePackageSpec(&p) require.Error(t, err) require.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) require.Error(t, err) require.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) require.Error(t, err) require.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) require.Error(t, err) require.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) require.Error(t, err) require.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) require.Error(t, err) require.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) require.Error(t, err) require.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) require.Error(t, err) require.Contains(t, err.Error(), "backup file contains leading forward slash") }) } func TestDescAndFileString(t *testing.T) { const pkgDesc = `%FILENAME% zstd-1.5.5-1-x86_64.pkg.tar.zst %NAME% zstd %BASE% zstd %VERSION% 1.5.5-1 %DESC% Zstandard - Fast real-time compression algorithm %GROUPS% dummy1 dummy2 %CSIZE% 401 %ISIZE% 1500453 %MD5SUM% 5016660ef3d9aa148a7b72a08d3df1b2 %SHA256SUM% 9fa4ede47e35f5971e4f26ecadcbfb66ab79f1d638317ac80334a3362dedbabd %URL% https://facebook.github.io/zstd/ %LICENSE% BSD GPL2 %ARCH% x86_64 %BUILDDATE% 1681646714 %PACKAGER% Jelle van der Waa %PROVIDES% libzstd.so=1-64 %DEPENDS% glibc gcc-libs zlib xz lz4 %OPTDEPENDS% dummy3 dummy4 %MAKEDEPENDS% cmake gtest ninja %CHECKDEPENDS% dummy5 dummy6 ` const pkgFiles = `%FILES% usr/ usr/bin/ usr/bin/zstd ` md := &Package{ CompressType: "zst", 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", Files: []string{"usr/", "usr/bin/", "usr/bin/zstd"}, }, } require.Equal(t, pkgDesc, md.Desc()) require.Equal(t, pkgFiles, md.Files()) }