You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

metadata.go 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package swift
  4. import (
  5. "archive/zip"
  6. "fmt"
  7. "io"
  8. "path"
  9. "regexp"
  10. "strings"
  11. "code.gitea.io/gitea/modules/json"
  12. "code.gitea.io/gitea/modules/util"
  13. "code.gitea.io/gitea/modules/validation"
  14. "github.com/hashicorp/go-version"
  15. )
  16. var (
  17. ErrMissingManifestFile = util.NewInvalidArgumentErrorf("Package.swift file is missing")
  18. ErrManifestFileTooLarge = util.NewInvalidArgumentErrorf("Package.swift file is too large")
  19. ErrInvalidManifestVersion = util.NewInvalidArgumentErrorf("manifest version is invalid")
  20. manifestPattern = regexp.MustCompile(`\APackage(?:@swift-(\d+(?:\.\d+)?(?:\.\d+)?))?\.swift\z`)
  21. toolsVersionPattern = regexp.MustCompile(`\A// swift-tools-version:(\d+(?:\.\d+)?(?:\.\d+)?)`)
  22. )
  23. const (
  24. maxManifestFileSize = 128 * 1024
  25. PropertyScope = "swift.scope"
  26. PropertyName = "swift.name"
  27. PropertyRepositoryURL = "swift.repository_url"
  28. )
  29. // Package represents a Swift package
  30. type Package struct {
  31. RepositoryURLs []string
  32. Metadata *Metadata
  33. }
  34. // Metadata represents the metadata of a Swift package
  35. type Metadata struct {
  36. Description string `json:"description,omitempty"`
  37. Keywords []string `json:"keywords,omitempty"`
  38. RepositoryURL string `json:"repository_url,omitempty"`
  39. License string `json:"license,omitempty"`
  40. Author Person `json:"author,omitempty"`
  41. Manifests map[string]*Manifest `json:"manifests,omitempty"`
  42. }
  43. // Manifest represents a Package.swift file
  44. type Manifest struct {
  45. Content string `json:"content"`
  46. ToolsVersion string `json:"tools_version,omitempty"`
  47. }
  48. // https://schema.org/SoftwareSourceCode
  49. type SoftwareSourceCode struct {
  50. Context []string `json:"@context"`
  51. Type string `json:"@type"`
  52. Name string `json:"name"`
  53. Version string `json:"version"`
  54. Description string `json:"description,omitempty"`
  55. Keywords []string `json:"keywords,omitempty"`
  56. CodeRepository string `json:"codeRepository,omitempty"`
  57. License string `json:"license,omitempty"`
  58. Author Person `json:"author"`
  59. ProgrammingLanguage ProgrammingLanguage `json:"programmingLanguage"`
  60. RepositoryURLs []string `json:"repositoryURLs,omitempty"`
  61. }
  62. // https://schema.org/ProgrammingLanguage
  63. type ProgrammingLanguage struct {
  64. Type string `json:"@type"`
  65. Name string `json:"name"`
  66. URL string `json:"url"`
  67. }
  68. // https://schema.org/Person
  69. type Person struct {
  70. Type string `json:"@type,omitempty"`
  71. GivenName string `json:"givenName,omitempty"`
  72. MiddleName string `json:"middleName,omitempty"`
  73. FamilyName string `json:"familyName,omitempty"`
  74. }
  75. func (p Person) String() string {
  76. var sb strings.Builder
  77. if p.GivenName != "" {
  78. sb.WriteString(p.GivenName)
  79. }
  80. if p.MiddleName != "" {
  81. if sb.Len() > 0 {
  82. sb.WriteRune(' ')
  83. }
  84. sb.WriteString(p.MiddleName)
  85. }
  86. if p.FamilyName != "" {
  87. if sb.Len() > 0 {
  88. sb.WriteRune(' ')
  89. }
  90. sb.WriteString(p.FamilyName)
  91. }
  92. return sb.String()
  93. }
  94. // ParsePackage parses the Swift package upload
  95. func ParsePackage(sr io.ReaderAt, size int64, mr io.Reader) (*Package, error) {
  96. zr, err := zip.NewReader(sr, size)
  97. if err != nil {
  98. return nil, err
  99. }
  100. p := &Package{
  101. Metadata: &Metadata{
  102. Manifests: make(map[string]*Manifest),
  103. },
  104. }
  105. for _, file := range zr.File {
  106. manifestMatch := manifestPattern.FindStringSubmatch(path.Base(file.Name))
  107. if len(manifestMatch) == 0 {
  108. continue
  109. }
  110. if file.UncompressedSize64 > maxManifestFileSize {
  111. return nil, ErrManifestFileTooLarge
  112. }
  113. f, err := zr.Open(file.Name)
  114. if err != nil {
  115. return nil, err
  116. }
  117. content, err := io.ReadAll(f)
  118. if err := f.Close(); err != nil {
  119. return nil, err
  120. }
  121. if err != nil {
  122. return nil, err
  123. }
  124. swiftVersion := ""
  125. if len(manifestMatch) == 2 && manifestMatch[1] != "" {
  126. v, err := version.NewSemver(manifestMatch[1])
  127. if err != nil {
  128. return nil, ErrInvalidManifestVersion
  129. }
  130. swiftVersion = TrimmedVersionString(v)
  131. }
  132. manifest := &Manifest{
  133. Content: string(content),
  134. }
  135. toolsMatch := toolsVersionPattern.FindStringSubmatch(manifest.Content)
  136. if len(toolsMatch) == 2 {
  137. v, err := version.NewSemver(toolsMatch[1])
  138. if err != nil {
  139. return nil, ErrInvalidManifestVersion
  140. }
  141. manifest.ToolsVersion = TrimmedVersionString(v)
  142. }
  143. p.Metadata.Manifests[swiftVersion] = manifest
  144. }
  145. if _, found := p.Metadata.Manifests[""]; !found {
  146. return nil, ErrMissingManifestFile
  147. }
  148. if mr != nil {
  149. var ssc *SoftwareSourceCode
  150. if err := json.NewDecoder(mr).Decode(&ssc); err != nil {
  151. return nil, err
  152. }
  153. p.Metadata.Description = ssc.Description
  154. p.Metadata.Keywords = ssc.Keywords
  155. p.Metadata.License = ssc.License
  156. p.Metadata.Author = Person{
  157. GivenName: ssc.Author.GivenName,
  158. MiddleName: ssc.Author.MiddleName,
  159. FamilyName: ssc.Author.FamilyName,
  160. }
  161. p.Metadata.RepositoryURL = ssc.CodeRepository
  162. if !validation.IsValidURL(p.Metadata.RepositoryURL) {
  163. p.Metadata.RepositoryURL = ""
  164. }
  165. p.RepositoryURLs = ssc.RepositoryURLs
  166. }
  167. return p, nil
  168. }
  169. // TrimmedVersionString returns the version string without the patch segment if it is zero
  170. func TrimmedVersionString(v *version.Version) string {
  171. segments := v.Segments64()
  172. var b strings.Builder
  173. fmt.Fprintf(&b, "%d.%d", segments[0], segments[1])
  174. if segments[2] != 0 {
  175. fmt.Fprintf(&b, ".%d", segments[2])
  176. }
  177. return b.String()
  178. }