gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package arch
  4. import (
  5. "archive/tar"
  6. "bufio"
  7. "bytes"
  8. "compress/gzip"
  9. "io"
  10. "regexp"
  11. "strconv"
  12. "strings"
  13. "code.gitea.io/gitea/modules/util"
  14. "code.gitea.io/gitea/modules/validation"
  15. "github.com/klauspost/compress/zstd"
  16. "github.com/ulikunitz/xz"
  17. )
  18. const (
  19. PropertyRepository = "arch.repository"
  20. PropertyArchitecture = "arch.architecture"
  21. PropertySignature = "arch.signature"
  22. PropertyMetadata = "arch.metadata"
  23. SettingKeyPrivate = "arch.key.private"
  24. SettingKeyPublic = "arch.key.public"
  25. RepositoryPackage = "_arch"
  26. RepositoryVersion = "_repository"
  27. AnyArch = "any"
  28. )
  29. var (
  30. ErrMissingPKGINFOFile = util.NewInvalidArgumentErrorf(".PKGINFO file is missing")
  31. ErrUnsupportedFormat = util.NewInvalidArgumentErrorf("unsupported package container format")
  32. ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
  33. ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
  34. ErrInvalidArchitecture = util.NewInvalidArgumentErrorf("package architecture is invalid")
  35. // https://man.archlinux.org/man/PKGBUILD.5
  36. namePattern = regexp.MustCompile(`\A[a-zA-Z0-9@._+-]+\z`)
  37. // (epoch:pkgver-pkgrel)
  38. versionPattern = regexp.MustCompile(`\A(?:\d:)?[\w.+~]+(?:-[-\w.+~]+)?\z`)
  39. )
  40. type Package struct {
  41. Name string
  42. Version string
  43. VersionMetadata VersionMetadata
  44. FileMetadata FileMetadata
  45. FileCompressionExtension string
  46. }
  47. type VersionMetadata struct {
  48. Description string `json:"description,omitempty"`
  49. ProjectURL string `json:"project_url,omitempty"`
  50. Licenses []string `json:"licenses,omitempty"`
  51. }
  52. type FileMetadata struct {
  53. Architecture string `json:"architecture"`
  54. Base string `json:"base,omitempty"`
  55. InstalledSize int64 `json:"installed_size,omitempty"`
  56. BuildDate int64 `json:"build_date,omitempty"`
  57. Packager string `json:"packager,omitempty"`
  58. Groups []string `json:"groups,omitempty"`
  59. Provides []string `json:"provides,omitempty"`
  60. Replaces []string `json:"replaces,omitempty"`
  61. Depends []string `json:"depends,omitempty"`
  62. OptDepends []string `json:"opt_depends,omitempty"`
  63. MakeDepends []string `json:"make_depends,omitempty"`
  64. CheckDepends []string `json:"check_depends,omitempty"`
  65. Conflicts []string `json:"conflicts,omitempty"`
  66. XData []string `json:"xdata,omitempty"`
  67. Backup []string `json:"backup,omitempty"`
  68. Files []string `json:"files,omitempty"`
  69. }
  70. // ParsePackage parses an Arch package file
  71. func ParsePackage(r io.Reader) (*Package, error) {
  72. header := make([]byte, 10)
  73. n, err := util.ReadAtMost(r, header)
  74. if err != nil {
  75. return nil, err
  76. }
  77. r = io.MultiReader(bytes.NewReader(header[:n]), r)
  78. var inner io.Reader
  79. var compressionType string
  80. if bytes.HasPrefix(header, []byte{0x28, 0xB5, 0x2F, 0xFD}) { // zst
  81. zr, err := zstd.NewReader(r)
  82. if err != nil {
  83. return nil, err
  84. }
  85. defer zr.Close()
  86. inner = zr
  87. compressionType = "zst"
  88. } else if bytes.HasPrefix(header, []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A}) { // xz
  89. xzr, err := xz.NewReader(r)
  90. if err != nil {
  91. return nil, err
  92. }
  93. inner = xzr
  94. compressionType = "xz"
  95. } else if bytes.HasPrefix(header, []byte{0x1F, 0x8B}) { // gz
  96. gzr, err := gzip.NewReader(r)
  97. if err != nil {
  98. return nil, err
  99. }
  100. defer gzr.Close()
  101. inner = gzr
  102. compressionType = "gz"
  103. } else {
  104. return nil, ErrUnsupportedFormat
  105. }
  106. var p *Package
  107. files := make([]string, 0, 10)
  108. tr := tar.NewReader(inner)
  109. for {
  110. hd, err := tr.Next()
  111. if err == io.EOF {
  112. break
  113. }
  114. if err != nil {
  115. return nil, err
  116. }
  117. if hd.Typeflag != tar.TypeReg {
  118. continue
  119. }
  120. filename := hd.FileInfo().Name()
  121. if filename == ".PKGINFO" {
  122. p, err = ParsePackageInfo(tr)
  123. if err != nil {
  124. return nil, err
  125. }
  126. } else if !strings.HasPrefix(filename, ".") {
  127. files = append(files, hd.Name)
  128. }
  129. }
  130. if p == nil {
  131. return nil, ErrMissingPKGINFOFile
  132. }
  133. p.FileMetadata.Files = files
  134. p.FileCompressionExtension = compressionType
  135. return p, nil
  136. }
  137. // ParsePackageInfo parses a .PKGINFO file to retrieve the metadata
  138. // https://man.archlinux.org/man/PKGBUILD.5
  139. // https://gitlab.archlinux.org/pacman/pacman/-/blob/master/lib/libalpm/be_package.c#L161
  140. func ParsePackageInfo(r io.Reader) (*Package, error) {
  141. p := &Package{}
  142. s := bufio.NewScanner(r)
  143. for s.Scan() {
  144. line := s.Text()
  145. if strings.HasPrefix(line, "#") {
  146. continue
  147. }
  148. i := strings.IndexRune(line, '=')
  149. if i == -1 {
  150. continue
  151. }
  152. key := strings.TrimSpace(line[:i])
  153. value := strings.TrimSpace(line[i+1:])
  154. switch key {
  155. case "pkgname":
  156. p.Name = value
  157. case "pkgbase":
  158. p.FileMetadata.Base = value
  159. case "pkgver":
  160. p.Version = value
  161. case "pkgdesc":
  162. p.VersionMetadata.Description = value
  163. case "url":
  164. p.VersionMetadata.ProjectURL = value
  165. case "packager":
  166. p.FileMetadata.Packager = value
  167. case "arch":
  168. p.FileMetadata.Architecture = value
  169. case "license":
  170. p.VersionMetadata.Licenses = append(p.VersionMetadata.Licenses, value)
  171. case "provides":
  172. p.FileMetadata.Provides = append(p.FileMetadata.Provides, value)
  173. case "depend":
  174. p.FileMetadata.Depends = append(p.FileMetadata.Depends, value)
  175. case "replaces":
  176. p.FileMetadata.Replaces = append(p.FileMetadata.Replaces, value)
  177. case "optdepend":
  178. p.FileMetadata.OptDepends = append(p.FileMetadata.OptDepends, value)
  179. case "makedepend":
  180. p.FileMetadata.MakeDepends = append(p.FileMetadata.MakeDepends, value)
  181. case "checkdepend":
  182. p.FileMetadata.CheckDepends = append(p.FileMetadata.CheckDepends, value)
  183. case "conflict":
  184. p.FileMetadata.Conflicts = append(p.FileMetadata.Conflicts, value)
  185. case "backup":
  186. p.FileMetadata.Backup = append(p.FileMetadata.Backup, value)
  187. case "group":
  188. p.FileMetadata.Groups = append(p.FileMetadata.Groups, value)
  189. case "builddate":
  190. date, err := strconv.ParseInt(value, 10, 64)
  191. if err != nil {
  192. return nil, err
  193. }
  194. p.FileMetadata.BuildDate = date
  195. case "size":
  196. size, err := strconv.ParseInt(value, 10, 64)
  197. if err != nil {
  198. return nil, err
  199. }
  200. p.FileMetadata.InstalledSize = size
  201. case "xdata":
  202. p.FileMetadata.XData = append(p.FileMetadata.XData, value)
  203. }
  204. }
  205. if err := s.Err(); err != nil {
  206. return nil, err
  207. }
  208. if !namePattern.MatchString(p.Name) {
  209. return nil, ErrInvalidName
  210. }
  211. if !versionPattern.MatchString(p.Version) {
  212. return nil, ErrInvalidVersion
  213. }
  214. if p.FileMetadata.Architecture == "" {
  215. return nil, ErrInvalidArchitecture
  216. }
  217. if !validation.IsValidURL(p.VersionMetadata.ProjectURL) {
  218. p.VersionMetadata.ProjectURL = ""
  219. }
  220. return p, nil
  221. }