gitea源码

metadata.go 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package alpine
  4. import (
  5. "archive/tar"
  6. "bufio"
  7. "compress/gzip"
  8. "crypto/sha1"
  9. "encoding/base64"
  10. "io"
  11. "strconv"
  12. "strings"
  13. "code.gitea.io/gitea/modules/util"
  14. "code.gitea.io/gitea/modules/validation"
  15. )
  16. var (
  17. ErrMissingPKGINFOFile = util.NewInvalidArgumentErrorf("PKGINFO file is missing")
  18. ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
  19. ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
  20. )
  21. const (
  22. PropertyMetadata = "alpine.metadata"
  23. PropertyBranch = "alpine.branch"
  24. PropertyRepository = "alpine.repository"
  25. PropertyArchitecture = "alpine.architecture"
  26. SettingKeyPrivate = "alpine.key.private"
  27. SettingKeyPublic = "alpine.key.public"
  28. RepositoryPackage = "_alpine"
  29. RepositoryVersion = "_repository"
  30. NoArch = "noarch"
  31. )
  32. // https://wiki.alpinelinux.org/wiki/Apk_spec
  33. // Package represents an Alpine package
  34. type Package struct {
  35. Name string
  36. Version string
  37. VersionMetadata VersionMetadata
  38. FileMetadata FileMetadata
  39. }
  40. // Metadata of an Alpine package
  41. type VersionMetadata struct {
  42. Description string `json:"description,omitempty"`
  43. License string `json:"license,omitempty"`
  44. ProjectURL string `json:"project_url,omitempty"`
  45. Maintainer string `json:"maintainer,omitempty"`
  46. }
  47. type FileMetadata struct {
  48. Checksum string `json:"checksum"`
  49. Packager string `json:"packager,omitempty"`
  50. BuildDate int64 `json:"build_date,omitempty"`
  51. Size int64 `json:"size,omitempty"`
  52. Architecture string `json:"architecture,omitempty"`
  53. Origin string `json:"origin,omitempty"`
  54. CommitHash string `json:"commit_hash,omitempty"`
  55. InstallIf string `json:"install_if,omitempty"`
  56. Provides []string `json:"provides,omitempty"`
  57. Dependencies []string `json:"dependencies,omitempty"`
  58. ProviderPriority int64 `json:"provider_priority,omitempty"`
  59. }
  60. // ParsePackage parses the Alpine package file
  61. func ParsePackage(r io.Reader) (*Package, error) {
  62. // Alpine packages are concated .tar.gz streams. Usually the first stream contains the package metadata.
  63. br := bufio.NewReader(r) // needed for gzip Multistream
  64. h := sha1.New()
  65. gzr, err := gzip.NewReader(&teeByteReader{br, h})
  66. if err != nil {
  67. return nil, err
  68. }
  69. defer gzr.Close()
  70. for {
  71. gzr.Multistream(false)
  72. tr := tar.NewReader(gzr)
  73. for {
  74. hd, err := tr.Next()
  75. if err == io.EOF {
  76. break
  77. }
  78. if err != nil {
  79. return nil, err
  80. }
  81. if hd.Name == ".PKGINFO" {
  82. p, err := ParsePackageInfo(tr)
  83. if err != nil {
  84. return nil, err
  85. }
  86. // drain the reader
  87. for {
  88. if _, err := tr.Next(); err != nil {
  89. break
  90. }
  91. }
  92. p.FileMetadata.Checksum = "Q1" + base64.StdEncoding.EncodeToString(h.Sum(nil))
  93. return p, nil
  94. }
  95. }
  96. h = sha1.New()
  97. err = gzr.Reset(&teeByteReader{br, h})
  98. if err == io.EOF {
  99. break
  100. }
  101. if err != nil {
  102. return nil, err
  103. }
  104. }
  105. return nil, ErrMissingPKGINFOFile
  106. }
  107. // ParsePackageInfo parses a PKGINFO file to retrieve the metadata of an Alpine package
  108. func ParsePackageInfo(r io.Reader) (*Package, error) {
  109. p := &Package{}
  110. scanner := bufio.NewScanner(r)
  111. for scanner.Scan() {
  112. line := scanner.Text()
  113. if strings.HasPrefix(line, "#") {
  114. continue
  115. }
  116. i := strings.IndexRune(line, '=')
  117. if i == -1 {
  118. continue
  119. }
  120. key := strings.TrimSpace(line[:i])
  121. value := strings.TrimSpace(line[i+1:])
  122. switch key {
  123. case "pkgname":
  124. p.Name = value
  125. case "pkgver":
  126. p.Version = value
  127. case "pkgdesc":
  128. p.VersionMetadata.Description = value
  129. case "url":
  130. p.VersionMetadata.ProjectURL = value
  131. case "builddate":
  132. n, err := strconv.ParseInt(value, 10, 64)
  133. if err == nil {
  134. p.FileMetadata.BuildDate = n
  135. }
  136. case "size":
  137. n, err := strconv.ParseInt(value, 10, 64)
  138. if err == nil {
  139. p.FileMetadata.Size = n
  140. }
  141. case "arch":
  142. p.FileMetadata.Architecture = value
  143. case "origin":
  144. p.FileMetadata.Origin = value
  145. case "commit":
  146. p.FileMetadata.CommitHash = value
  147. case "maintainer":
  148. p.VersionMetadata.Maintainer = value
  149. case "packager":
  150. p.FileMetadata.Packager = value
  151. case "license":
  152. p.VersionMetadata.License = value
  153. case "install_if":
  154. p.FileMetadata.InstallIf = value
  155. case "provides":
  156. if value != "" {
  157. p.FileMetadata.Provides = append(p.FileMetadata.Provides, value)
  158. }
  159. case "depend":
  160. if value != "" {
  161. p.FileMetadata.Dependencies = append(p.FileMetadata.Dependencies, value)
  162. }
  163. case "provider_priority":
  164. n, err := strconv.ParseInt(value, 10, 64)
  165. if err == nil {
  166. p.FileMetadata.ProviderPriority = n
  167. }
  168. }
  169. }
  170. if err := scanner.Err(); err != nil {
  171. return nil, err
  172. }
  173. if p.Name == "" {
  174. return nil, ErrInvalidName
  175. }
  176. if p.Version == "" {
  177. return nil, ErrInvalidVersion
  178. }
  179. if !validation.IsValidURL(p.VersionMetadata.ProjectURL) {
  180. p.VersionMetadata.ProjectURL = ""
  181. }
  182. return p, nil
  183. }
  184. // Same as io.TeeReader but implements io.ByteReader
  185. type teeByteReader struct {
  186. r *bufio.Reader
  187. w io.Writer
  188. }
  189. func (t *teeByteReader) Read(p []byte) (int, error) {
  190. n, err := t.r.Read(p)
  191. if n > 0 {
  192. if n, err := t.w.Write(p[:n]); err != nil {
  193. return n, err
  194. }
  195. }
  196. return n, err
  197. }
  198. func (t *teeByteReader) ReadByte() (byte, error) {
  199. b, err := t.r.ReadByte()
  200. if err == nil {
  201. if _, err := t.w.Write([]byte{b}); err != nil {
  202. return 0, err
  203. }
  204. }
  205. return b, err
  206. }