-
-
Notifications
You must be signed in to change notification settings - Fork 6.2k
Arch packages implementation #31037
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Arch packages implementation #31037
Changes from 23 commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
fcb84f1
fix anything ...
ExplodingDragon c98a705
fix anything v2 ...
ExplodingDragon 97aefc5
fix anything v3 ...
ExplodingDragon 2667260
fix anything v4 ...
ExplodingDragon 317ea7f
fix defer error
ExplodingDragon fbe09b2
fix cache download
ExplodingDragon 0328200
fix some bugs
ExplodingDragon 7b53f05
fix ui bugs
ExplodingDragon 6978762
Merge branch 'main' into pacman-packages
ExplodingDragon c71fd97
fix url typo
ExplodingDragon d7373b5
Merge branch 'main' into pacman-packages
ExplodingDragon 5c2625a
fix typo and errors
ExplodingDragon b37e591
Merge branch 'main' into pacman-packages
ExplodingDragon bc72567
add locker
ExplodingDragon 3da98af
Merge branch 'main' into pacman-packages
ExplodingDragon f4092d6
fix tests
ExplodingDragon 90f3b30
Merge branch 'pacman-packages' of github.com:ExplodingDragon/gitea in…
ExplodingDragon 3caa520
add comments.
ExplodingDragon c35f354
add Concurrent tests.
ExplodingDragon 1b0a4e2
fix errors.
ExplodingDragon 1b256e4
fix errors.
ExplodingDragon 5aeccde
Merge branch 'main' into pacman-packages
ExplodingDragon d80ca1f
remove xdata check
ExplodingDragon 3c71f34
add more version checks
ExplodingDragon b3db785
Merge branch 'main' into pacman-packages
ExplodingDragon 568f7a6
clean style
ExplodingDragon 0b95ef2
fix bugs
ExplodingDragon 07c2b6d
fix typo
ExplodingDragon 8b63213
Merge branch 'main' into pacman-packages
ExplodingDragon 7eb9935
Merge branch 'main' into pacman-packages
ExplodingDragon bf0b534
Merge branch 'main' into pacman-packages
ExplodingDragon aee7007
Merge branch 'main' into pacman-packages
ExplodingDragon d679da8
Merge branch 'main' into pacman-packages
ExplodingDragon e07ddcb
Merge branch 'main' into pacman-packages
wxiaoguang 84ff353
fix
wxiaoguang ce73234
use PackageRegistryHost
wxiaoguang 76554fe
fix test
wxiaoguang f68456f
fix bugs from forgejo
ExplodingDragon 590e17b
Merge branch 'pacman-packages' of github.com:ExplodingDragon/gitea in…
ExplodingDragon 5e959b0
Merge branch 'main' into pacman-packages
ExplodingDragon 15f7371
improve test
wxiaoguang b460e0d
Merge branch 'main' into pacman-packages
wxiaoguang 26c06a3
improve
wxiaoguang 5d7eba0
fix template
wxiaoguang be00230
Merge branch 'main' into pacman-packages
wxiaoguang dc8d778
add comments and fine tune test names
wxiaoguang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,342 @@ | ||
| // Copyright 2023 The Gitea Authors. All rights reserved. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package arch | ||
|
|
||
| import ( | ||
| "bufio" | ||
| "bytes" | ||
| "encoding/hex" | ||
| "errors" | ||
| "fmt" | ||
| "io" | ||
| "regexp" | ||
| "strconv" | ||
| "strings" | ||
|
|
||
| "code.gitea.io/gitea/modules/packages" | ||
| "code.gitea.io/gitea/modules/util" | ||
| "code.gitea.io/gitea/modules/validation" | ||
|
|
||
| "github.com/mholt/archiver/v3" | ||
| ) | ||
|
|
||
| // Arch Linux Packages | ||
| // https://man.archlinux.org/man/PKGBUILD.5 | ||
|
|
||
| const ( | ||
| PropertyDescription = "arch.description" | ||
| PropertyArch = "arch.architecture" | ||
| PropertyDistribution = "arch.distribution" | ||
|
|
||
| SettingKeyPrivate = "arch.key.private" | ||
| SettingKeyPublic = "arch.key.public" | ||
|
|
||
| RepositoryPackage = "_arch" | ||
| RepositoryVersion = "_repository" | ||
| ) | ||
|
|
||
| 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@._+-]+)?$`) | ||
|
|
||
| magicZSTD = []byte{0x28, 0xB5, 0x2F, 0xFD} | ||
| magicXZ = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A} | ||
| magicGZ = []byte{0x1F, 0x8B} | ||
| ) | ||
|
|
||
| type Package struct { | ||
| Name string `json:"name"` | ||
| Version string `json:"version"` // Includes version, release and epoch | ||
| CompressType string `json:"compress_type"` | ||
| 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"` | ||
| Conflicts []string `json:"conflicts,omitempty"` | ||
| Replaces []string `json:"replaces,omitempty"` | ||
| Backup []string `json:"backup,omitempty"` | ||
| XData []string `json:"xdata,omitempty"` | ||
| } | ||
|
|
||
| // FileMetadata Metadata related to specific package 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"` | ||
| PgpSigned string `json:"pgp"` | ||
| } | ||
|
|
||
| // ParsePackage Function that receives arch package archive data and returns it's metadata. | ||
| func ParsePackage(r *packages.HashedBuffer) (*Package, error) { | ||
| md5, _, sha256, _ := r.Sums() | ||
| _, err := r.Seek(0, io.SeekStart) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| header := make([]byte, 5) | ||
| _, err = r.Read(header) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| _, err = r.Seek(0, io.SeekStart) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| var tarball archiver.Reader | ||
| var tarballType string | ||
| if bytes.Equal(header[:len(magicZSTD)], magicZSTD) { | ||
| tarballType = "zst" | ||
| tarball = archiver.NewTarZstd() | ||
| } else if bytes.Equal(header[:len(magicXZ)], magicXZ) { | ||
| tarballType = "xz" | ||
| tarball = archiver.NewTarXz() | ||
| } else if bytes.Equal(header[:len(magicGZ)], magicGZ) { | ||
| tarballType = "gz" | ||
| tarball = archiver.NewTarGz() | ||
| } else { | ||
| return nil, errors.New("not supported compression") | ||
| } | ||
| err = tarball.Open(r, 0) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer tarball.Close() | ||
|
|
||
| var pkg *Package | ||
| var mtree bool | ||
|
|
||
| for { | ||
| f, err := tarball.Read() | ||
| if err == io.EOF { | ||
| break | ||
| } | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| switch f.Name() { | ||
| case ".PKGINFO": | ||
| pkg, err = ParsePackageInfo(tarballType, f) | ||
| if err != nil { | ||
| _ = f.Close() | ||
| return nil, err | ||
ExplodingDragon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| case ".MTREE": | ||
| mtree = true | ||
| } | ||
| _ = f.Close() | ||
| } | ||
|
|
||
| if pkg == nil { | ||
| return nil, util.NewInvalidArgumentErrorf(".PKGINFO file not found") | ||
| } | ||
|
|
||
| if !mtree { | ||
| return nil, util.NewInvalidArgumentErrorf(".MTREE file not found") | ||
| } | ||
|
|
||
| pkg.FileMetadata.CompressedSize = r.Size() | ||
| pkg.FileMetadata.MD5 = hex.EncodeToString(md5) | ||
| pkg.FileMetadata.SHA256 = hex.EncodeToString(sha256) | ||
|
|
||
| return pkg, nil | ||
| } | ||
|
|
||
| // ParsePackageInfo Function that accepts reader for .PKGINFO file from package archive, | ||
| // validates all field according to PKGBUILD spec and returns package. | ||
| func ParsePackageInfo(compressType string, r io.Reader) (*Package, error) { | ||
| p := &Package{ | ||
| CompressType: compressType, | ||
| } | ||
|
|
||
| scanner := bufio.NewScanner(r) | ||
| for scanner.Scan() { | ||
| line := scanner.Text() | ||
|
|
||
| if strings.HasPrefix(line, "#") { | ||
| continue | ||
| } | ||
|
|
||
| key, value, find := strings.Cut(line, "=") | ||
lunny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if !find { | ||
| continue | ||
| } | ||
| key = strings.TrimSpace(key) | ||
| value = strings.TrimSpace(value) | ||
| 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 "conflict": | ||
| p.VersionMetadata.Conflicts = append(p.VersionMetadata.Conflicts, value) | ||
| case "replaces": | ||
| p.VersionMetadata.Replaces = append(p.VersionMetadata.Replaces, value) | ||
| case "xdata": | ||
| p.VersionMetadata.XData = append(p.VersionMetadata.XData, 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 | ||
| default: | ||
| return nil, util.NewInvalidArgumentErrorf("property is not supported %s", key) | ||
| } | ||
| } | ||
|
|
||
| return p, errors.Join(scanner.Err(), ValidatePackageSpec(p)) | ||
| } | ||
|
|
||
| // ValidatePackageSpec Arch package validation according to PKGBUILD specification. | ||
| func ValidatePackageSpec(p *Package) error { | ||
wxiaoguang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 _, checkDepend := range p.VersionMetadata.CheckDepends { | ||
| if !rePkgVer.MatchString(checkDepend) { | ||
| return util.NewInvalidArgumentErrorf("invalid check dependency: %s", checkDepend) | ||
| } | ||
| } | ||
| for _, depend := range p.VersionMetadata.Depends { | ||
| if !rePkgVer.MatchString(depend) { | ||
| return util.NewInvalidArgumentErrorf("invalid dependency: %s", depend) | ||
| } | ||
| } | ||
| for _, makeDepend := range p.VersionMetadata.MakeDepends { | ||
| if !rePkgVer.MatchString(makeDepend) { | ||
| return util.NewInvalidArgumentErrorf("invalid make dependency: %s ", makeDepend) | ||
| } | ||
| } | ||
| for _, provide := range p.VersionMetadata.Provides { | ||
| if !rePkgVer.MatchString(provide) { | ||
| return util.NewInvalidArgumentErrorf("invalid provides: %s", provide) | ||
| } | ||
| } | ||
| for _, conflict := range p.VersionMetadata.Conflicts { | ||
| if !rePkgVer.MatchString(conflict) { | ||
| return util.NewInvalidArgumentErrorf("invalid conflicts: %s", conflict) | ||
| } | ||
| } | ||
| for _, replace := range p.VersionMetadata.Replaces { | ||
| if !rePkgVer.MatchString(replace) { | ||
| return util.NewInvalidArgumentErrorf("invalid replaces: %s", replace) | ||
| } | ||
| } | ||
| for _, optDepend := range p.VersionMetadata.OptDepends { | ||
| if !reOptDep.MatchString(optDepend) { | ||
| return util.NewInvalidArgumentErrorf("invalid optional dependency: %s", optDepend) | ||
| } | ||
| } | ||
| for _, backup := range p.VersionMetadata.Backup { | ||
| if strings.HasPrefix(backup, "/") { | ||
| return util.NewInvalidArgumentErrorf("backup file contains leading forward slash") | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // Desc Create pacman package description file. | ||
| func (p *Package) Desc() string { | ||
wxiaoguang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| entries := []string{ | ||
| "FILENAME", fmt.Sprintf("%s-%s-%s.pkg.tar.%s", p.Name, p.Version, p.FileMetadata.Arch, p.CompressType), | ||
| "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, | ||
| "PGPSIG", p.FileMetadata.PgpSigned, | ||
| "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, | ||
| "REPLACES", strings.Join(p.VersionMetadata.Replaces, "\n"), | ||
| "CONFLICTS", strings.Join(p.VersionMetadata.Conflicts, "\n"), | ||
| "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 buf bytes.Buffer | ||
| for i := 0; i < len(entries); i += 2 { | ||
| if entries[i+1] != "" { | ||
| _, _ = fmt.Fprintf(&buf, "%%%s%%\n%s\n\n", entries[i], entries[i+1]) | ||
| } | ||
| } | ||
| return buf.String() | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.