gitea源码

metadata.go 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package composer
  4. import (
  5. "archive/zip"
  6. "io"
  7. "path"
  8. "regexp"
  9. "strings"
  10. "code.gitea.io/gitea/modules/json"
  11. "code.gitea.io/gitea/modules/util"
  12. "code.gitea.io/gitea/modules/validation"
  13. "github.com/hashicorp/go-version"
  14. )
  15. // TypeProperty is the name of the property for Composer package types
  16. const TypeProperty = "composer.type"
  17. var (
  18. // ErrMissingComposerFile indicates a missing composer.json file
  19. ErrMissingComposerFile = util.NewInvalidArgumentErrorf("composer.json file is missing")
  20. // ErrInvalidName indicates an invalid package name
  21. ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
  22. // ErrInvalidVersion indicates an invalid package version
  23. ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
  24. )
  25. // Package represents a Composer package
  26. type Package struct {
  27. Name string
  28. Version string
  29. Type string
  30. Metadata *Metadata
  31. }
  32. // https://getcomposer.org/doc/04-schema.md
  33. // Metadata represents the metadata of a Composer package
  34. type Metadata struct {
  35. Description string `json:"description,omitempty"`
  36. Readme string `json:"readme,omitempty"`
  37. Keywords []string `json:"keywords,omitempty"`
  38. Comments Comments `json:"_comments,omitempty"`
  39. Homepage string `json:"homepage,omitempty"`
  40. License Licenses `json:"license,omitempty"`
  41. Authors []Author `json:"authors,omitempty"`
  42. Bin []string `json:"bin,omitempty"`
  43. Autoload map[string]any `json:"autoload,omitempty"`
  44. AutoloadDev map[string]any `json:"autoload-dev,omitempty"`
  45. Extra map[string]any `json:"extra,omitempty"`
  46. Require map[string]string `json:"require,omitempty"`
  47. RequireDev map[string]string `json:"require-dev,omitempty"`
  48. Suggest map[string]string `json:"suggest,omitempty"`
  49. Provide map[string]string `json:"provide,omitempty"`
  50. }
  51. // Licenses represents the licenses of a Composer package
  52. type Licenses []string
  53. // UnmarshalJSON reads from a string or array
  54. func (l *Licenses) UnmarshalJSON(data []byte) error {
  55. switch data[0] {
  56. case '"':
  57. var value string
  58. if err := json.Unmarshal(data, &value); err != nil {
  59. return err
  60. }
  61. *l = Licenses{value}
  62. case '[':
  63. values := make([]string, 0, 5)
  64. if err := json.Unmarshal(data, &values); err != nil {
  65. return err
  66. }
  67. *l = Licenses(values)
  68. }
  69. return nil
  70. }
  71. // Comments represents the comments of a Composer package
  72. type Comments []string
  73. // UnmarshalJSON reads from a string or array
  74. func (c *Comments) UnmarshalJSON(data []byte) error {
  75. switch data[0] {
  76. case '"':
  77. var value string
  78. if err := json.Unmarshal(data, &value); err != nil {
  79. return err
  80. }
  81. *c = Comments{value}
  82. case '[':
  83. values := make([]string, 0, 5)
  84. if err := json.Unmarshal(data, &values); err != nil {
  85. return err
  86. }
  87. *c = Comments(values)
  88. }
  89. return nil
  90. }
  91. // Author represents an author
  92. type Author struct {
  93. Name string `json:"name,omitempty"`
  94. Email string `json:"email,omitempty"`
  95. Homepage string `json:"homepage,omitempty"`
  96. }
  97. var nameMatch = regexp.MustCompile(`\A[a-z0-9]([_\.-]?[a-z0-9]+)*/[a-z0-9](([_\.]?|-{0,2})[a-z0-9]+)*\z`)
  98. // ParsePackage parses the metadata of a Composer package file
  99. func ParsePackage(r io.ReaderAt, size int64) (*Package, error) {
  100. archive, err := zip.NewReader(r, size)
  101. if err != nil {
  102. return nil, err
  103. }
  104. for _, file := range archive.File {
  105. if strings.Count(file.Name, "/") > 1 {
  106. continue
  107. }
  108. if strings.HasSuffix(strings.ToLower(file.Name), "composer.json") {
  109. f, err := archive.Open(file.Name)
  110. if err != nil {
  111. return nil, err
  112. }
  113. defer f.Close()
  114. return ParseComposerFile(archive, path.Dir(file.Name), f)
  115. }
  116. }
  117. return nil, ErrMissingComposerFile
  118. }
  119. // ParseComposerFile parses a composer.json file to retrieve the metadata of a Composer package
  120. func ParseComposerFile(archive *zip.Reader, pathPrefix string, r io.Reader) (*Package, error) {
  121. var cj struct {
  122. Name string `json:"name"`
  123. Version string `json:"version"`
  124. Type string `json:"type"`
  125. Metadata
  126. }
  127. if err := json.NewDecoder(r).Decode(&cj); err != nil {
  128. return nil, err
  129. }
  130. if !nameMatch.MatchString(cj.Name) {
  131. return nil, ErrInvalidName
  132. }
  133. if cj.Version != "" {
  134. if _, err := version.NewSemver(cj.Version); err != nil {
  135. return nil, ErrInvalidVersion
  136. }
  137. }
  138. if !validation.IsValidURL(cj.Homepage) {
  139. cj.Homepage = ""
  140. }
  141. if cj.Type == "" {
  142. cj.Type = "library"
  143. }
  144. if cj.Readme == "" {
  145. cj.Readme = "README.md"
  146. }
  147. f, err := archive.Open(path.Join(pathPrefix, cj.Readme))
  148. if err == nil {
  149. // 10kb limit for readme content
  150. buf, _ := io.ReadAll(io.LimitReader(f, 10*1024))
  151. cj.Readme = string(buf)
  152. _ = f.Close()
  153. } else {
  154. cj.Readme = ""
  155. }
  156. return &Package{
  157. Name: cj.Name,
  158. Version: cj.Version,
  159. Type: cj.Type,
  160. Metadata: &cj.Metadata,
  161. }, nil
  162. }