gitea源码

metadata.go 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package nuget
  4. import (
  5. "archive/zip"
  6. "bytes"
  7. "encoding/xml"
  8. "fmt"
  9. "io"
  10. "path/filepath"
  11. "regexp"
  12. "strings"
  13. "code.gitea.io/gitea/modules/util"
  14. "code.gitea.io/gitea/modules/validation"
  15. "github.com/hashicorp/go-version"
  16. )
  17. var (
  18. // ErrMissingNuspecFile indicates a missing Nuspec file
  19. ErrMissingNuspecFile = util.NewInvalidArgumentErrorf("Nuspec file is missing")
  20. // ErrNuspecFileTooLarge indicates a Nuspec file which is too large
  21. ErrNuspecFileTooLarge = util.NewInvalidArgumentErrorf("Nuspec file is too large")
  22. // ErrNuspecInvalidID indicates an invalid id in the Nuspec file
  23. ErrNuspecInvalidID = util.NewInvalidArgumentErrorf("Nuspec file contains an invalid id")
  24. // ErrNuspecInvalidVersion indicates an invalid version in the Nuspec file
  25. ErrNuspecInvalidVersion = util.NewInvalidArgumentErrorf("Nuspec file contains an invalid version")
  26. )
  27. // PackageType specifies the package type the metadata describes
  28. type PackageType int
  29. const (
  30. // DependencyPackage represents a package (*.nupkg)
  31. DependencyPackage PackageType = iota + 1
  32. // SymbolsPackage represents a symbol package (*.snupkg)
  33. SymbolsPackage
  34. PropertySymbolID = "nuget.symbol.id"
  35. )
  36. var idmatch = regexp.MustCompile(`\A\w+(?:[.-]\w+)*\z`)
  37. const maxNuspecFileSize = 3 * 1024 * 1024
  38. // Package represents a Nuget package
  39. type Package struct {
  40. PackageType PackageType
  41. ID string
  42. Version string
  43. Metadata *Metadata
  44. NuspecContent *bytes.Buffer
  45. }
  46. // Metadata represents the metadata of a Nuget package
  47. type Metadata struct {
  48. Authors string `json:"authors,omitempty"`
  49. Copyright string `json:"copyright,omitempty"`
  50. Description string `json:"description,omitempty"`
  51. DevelopmentDependency bool `json:"development_dependency,omitempty"`
  52. IconURL string `json:"icon_url,omitempty"`
  53. Language string `json:"language,omitempty"`
  54. LicenseURL string `json:"license_url,omitempty"`
  55. MinClientVersion string `json:"min_client_version,omitempty"`
  56. Owners string `json:"owners,omitempty"`
  57. ProjectURL string `json:"project_url,omitempty"`
  58. Readme string `json:"readme,omitempty"`
  59. ReleaseNotes string `json:"release_notes,omitempty"`
  60. RepositoryURL string `json:"repository_url,omitempty"`
  61. RequireLicenseAcceptance bool `json:"require_license_acceptance"`
  62. Summary string `json:"summary,omitempty"`
  63. Tags string `json:"tags,omitempty"`
  64. Title string `json:"title,omitempty"`
  65. Dependencies map[string][]Dependency `json:"dependencies,omitempty"`
  66. }
  67. // Dependency represents a dependency of a Nuget package
  68. type Dependency struct {
  69. ID string `json:"id"`
  70. Version string `json:"version"`
  71. }
  72. // https://learn.microsoft.com/en-us/nuget/reference/nuspec
  73. // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Packaging/compiler/resources/nuspec.xsd
  74. type nuspecPackage struct {
  75. Metadata struct {
  76. // required fields
  77. Authors string `xml:"authors"`
  78. Description string `xml:"description"`
  79. ID string `xml:"id"`
  80. Version string `xml:"version"`
  81. // optional fields
  82. Copyright string `xml:"copyright"`
  83. DevelopmentDependency bool `xml:"developmentDependency"`
  84. IconURL string `xml:"iconUrl"`
  85. Language string `xml:"language"`
  86. LicenseURL string `xml:"licenseUrl"`
  87. MinClientVersion string `xml:"minClientVersion,attr"`
  88. Owners string `xml:"owners"`
  89. ProjectURL string `xml:"projectUrl"`
  90. Readme string `xml:"readme"`
  91. ReleaseNotes string `xml:"releaseNotes"`
  92. RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"`
  93. Summary string `xml:"summary"`
  94. Tags string `xml:"tags"`
  95. Title string `xml:"title"`
  96. Dependencies struct {
  97. Dependency []struct {
  98. ID string `xml:"id,attr"`
  99. Version string `xml:"version,attr"`
  100. Exclude string `xml:"exclude,attr"`
  101. } `xml:"dependency"`
  102. Group []struct {
  103. TargetFramework string `xml:"targetFramework,attr"`
  104. Dependency []struct {
  105. ID string `xml:"id,attr"`
  106. Version string `xml:"version,attr"`
  107. Exclude string `xml:"exclude,attr"`
  108. } `xml:"dependency"`
  109. } `xml:"group"`
  110. } `xml:"dependencies"`
  111. PackageTypes struct {
  112. PackageType []struct {
  113. Name string `xml:"name,attr"`
  114. } `xml:"packageType"`
  115. } `xml:"packageTypes"`
  116. Repository struct {
  117. URL string `xml:"url,attr"`
  118. } `xml:"repository"`
  119. } `xml:"metadata"`
  120. }
  121. // ParsePackageMetaData parses the metadata of a Nuget package file
  122. func ParsePackageMetaData(r io.ReaderAt, size int64) (*Package, error) {
  123. archive, err := zip.NewReader(r, size)
  124. if err != nil {
  125. return nil, err
  126. }
  127. for _, file := range archive.File {
  128. if filepath.Dir(file.Name) != "." {
  129. continue
  130. }
  131. if strings.HasSuffix(strings.ToLower(file.Name), ".nuspec") {
  132. if file.UncompressedSize64 > maxNuspecFileSize {
  133. return nil, ErrNuspecFileTooLarge
  134. }
  135. f, err := archive.Open(file.Name)
  136. if err != nil {
  137. return nil, err
  138. }
  139. defer f.Close()
  140. return ParseNuspecMetaData(archive, f)
  141. }
  142. }
  143. return nil, ErrMissingNuspecFile
  144. }
  145. // ParseNuspecMetaData parses a Nuspec file to retrieve the metadata of a Nuget package
  146. func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
  147. var nuspecBuf bytes.Buffer
  148. var p nuspecPackage
  149. if err := xml.NewDecoder(io.TeeReader(r, &nuspecBuf)).Decode(&p); err != nil {
  150. return nil, err
  151. }
  152. if !idmatch.MatchString(p.Metadata.ID) {
  153. return nil, ErrNuspecInvalidID
  154. }
  155. v, err := version.NewSemver(p.Metadata.Version)
  156. if err != nil {
  157. return nil, ErrNuspecInvalidVersion
  158. }
  159. if !validation.IsValidURL(p.Metadata.ProjectURL) {
  160. p.Metadata.ProjectURL = ""
  161. }
  162. packageType := DependencyPackage
  163. for _, pt := range p.Metadata.PackageTypes.PackageType {
  164. if pt.Name == "SymbolsPackage" {
  165. packageType = SymbolsPackage
  166. break
  167. }
  168. }
  169. m := &Metadata{
  170. Authors: p.Metadata.Authors,
  171. Copyright: p.Metadata.Copyright,
  172. Description: p.Metadata.Description,
  173. DevelopmentDependency: p.Metadata.DevelopmentDependency,
  174. IconURL: p.Metadata.IconURL,
  175. Language: p.Metadata.Language,
  176. LicenseURL: p.Metadata.LicenseURL,
  177. MinClientVersion: p.Metadata.MinClientVersion,
  178. Owners: p.Metadata.Owners,
  179. ProjectURL: p.Metadata.ProjectURL,
  180. ReleaseNotes: p.Metadata.ReleaseNotes,
  181. RepositoryURL: p.Metadata.Repository.URL,
  182. RequireLicenseAcceptance: p.Metadata.RequireLicenseAcceptance,
  183. Summary: p.Metadata.Summary,
  184. Tags: p.Metadata.Tags,
  185. Title: p.Metadata.Title,
  186. Dependencies: make(map[string][]Dependency),
  187. }
  188. if p.Metadata.Readme != "" {
  189. f, err := archive.Open(p.Metadata.Readme)
  190. if err == nil {
  191. buf, _ := io.ReadAll(f)
  192. m.Readme = string(buf)
  193. _ = f.Close()
  194. }
  195. }
  196. if len(p.Metadata.Dependencies.Dependency) > 0 {
  197. deps := make([]Dependency, 0, len(p.Metadata.Dependencies.Dependency))
  198. for _, dep := range p.Metadata.Dependencies.Dependency {
  199. if dep.ID == "" || dep.Version == "" {
  200. continue
  201. }
  202. deps = append(deps, Dependency{
  203. ID: dep.ID,
  204. Version: dep.Version,
  205. })
  206. }
  207. m.Dependencies[""] = deps
  208. }
  209. for _, group := range p.Metadata.Dependencies.Group {
  210. deps := make([]Dependency, 0, len(group.Dependency))
  211. for _, dep := range group.Dependency {
  212. if dep.ID == "" || dep.Version == "" {
  213. continue
  214. }
  215. deps = append(deps, Dependency{
  216. ID: dep.ID,
  217. Version: dep.Version,
  218. })
  219. }
  220. if len(deps) > 0 {
  221. m.Dependencies[group.TargetFramework] = deps
  222. }
  223. }
  224. return &Package{
  225. PackageType: packageType,
  226. ID: p.Metadata.ID,
  227. Version: toNormalizedVersion(v),
  228. Metadata: m,
  229. NuspecContent: &nuspecBuf,
  230. }, nil
  231. }
  232. // https://learn.microsoft.com/en-us/nuget/concepts/package-versioning#normalized-version-numbers
  233. // https://github.com/NuGet/NuGet.Client/blob/dccbd304b11103e08b97abf4cf4bcc1499d9235a/src/NuGet.Core/NuGet.Versioning/VersionFormatter.cs#L121
  234. func toNormalizedVersion(v *version.Version) string {
  235. var buf bytes.Buffer
  236. segments := v.Segments64()
  237. _, _ = fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2])
  238. if len(segments) > 3 && segments[3] > 0 {
  239. _, _ = fmt.Fprintf(&buf, ".%d", segments[3])
  240. }
  241. pre := v.Prerelease()
  242. if pre != "" {
  243. _, _ = fmt.Fprint(&buf, "-", pre)
  244. }
  245. return buf.String()
  246. }