gitea源码

creator.go 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package npm
  4. import (
  5. "bytes"
  6. "crypto/sha1"
  7. "crypto/sha512"
  8. "encoding/base64"
  9. "fmt"
  10. "io"
  11. "regexp"
  12. "strings"
  13. "time"
  14. "code.gitea.io/gitea/modules/json"
  15. "code.gitea.io/gitea/modules/util"
  16. "code.gitea.io/gitea/modules/validation"
  17. "github.com/hashicorp/go-version"
  18. )
  19. var (
  20. // ErrInvalidPackage indicates an invalid package
  21. ErrInvalidPackage = util.NewInvalidArgumentErrorf("package is invalid")
  22. // ErrInvalidPackageName indicates an invalid name
  23. ErrInvalidPackageName = util.NewInvalidArgumentErrorf("package name is invalid")
  24. // ErrInvalidPackageVersion indicates an invalid version
  25. ErrInvalidPackageVersion = util.NewInvalidArgumentErrorf("package version is invalid")
  26. // ErrInvalidAttachment indicates a invalid attachment
  27. ErrInvalidAttachment = util.NewInvalidArgumentErrorf("package attachment is invalid")
  28. // ErrInvalidIntegrity indicates an integrity validation error
  29. ErrInvalidIntegrity = util.NewInvalidArgumentErrorf("failed to validate integrity")
  30. )
  31. var nameMatch = regexp.MustCompile(`^(@[a-z0-9-][a-z0-9-._]*/)?[a-z0-9-][a-z0-9-._]*$`)
  32. // Package represents a npm package
  33. type Package struct {
  34. Name string
  35. Version string
  36. DistTags []string
  37. Metadata Metadata
  38. Filename string
  39. Data []byte
  40. }
  41. // PackageMetadata https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#package
  42. type PackageMetadata struct {
  43. ID string `json:"_id"`
  44. Name string `json:"name"`
  45. Description string `json:"description"`
  46. DistTags map[string]string `json:"dist-tags,omitempty"`
  47. Versions map[string]*PackageMetadataVersion `json:"versions"`
  48. Readme string `json:"readme,omitempty"`
  49. Maintainers []User `json:"maintainers,omitempty"`
  50. Time map[string]time.Time `json:"time,omitempty"`
  51. Homepage string `json:"homepage,omitempty"`
  52. Keywords []string `json:"keywords,omitempty"`
  53. Repository Repository `json:"repository"`
  54. Author User `json:"author"`
  55. ReadmeFilename string `json:"readmeFilename,omitempty"`
  56. Users map[string]bool `json:"users,omitempty"`
  57. License string `json:"license,omitempty"`
  58. }
  59. // PackageMetadataVersion documentation: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version
  60. // PackageMetadataVersion response: https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md#abbreviated-version-object
  61. type PackageMetadataVersion struct {
  62. ID string `json:"_id"`
  63. Name string `json:"name"`
  64. Version string `json:"version"`
  65. Description string `json:"description"`
  66. Author User `json:"author"`
  67. Homepage string `json:"homepage,omitempty"`
  68. License string `json:"license,omitempty"`
  69. Repository Repository `json:"repository"`
  70. Keywords []string `json:"keywords,omitempty"`
  71. Dependencies map[string]string `json:"dependencies,omitempty"`
  72. BundleDependencies []string `json:"bundleDependencies,omitempty"`
  73. DevDependencies map[string]string `json:"devDependencies,omitempty"`
  74. PeerDependencies map[string]string `json:"peerDependencies,omitempty"`
  75. PeerDependenciesMeta map[string]any `json:"peerDependenciesMeta,omitempty"`
  76. Bin map[string]string `json:"bin,omitempty"`
  77. OptionalDependencies map[string]string `json:"optionalDependencies,omitempty"`
  78. Readme string `json:"readme,omitempty"`
  79. Dist PackageDistribution `json:"dist"`
  80. Maintainers []User `json:"maintainers,omitempty"`
  81. }
  82. // PackageDistribution https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version
  83. type PackageDistribution struct {
  84. Integrity string `json:"integrity"`
  85. Shasum string `json:"shasum"`
  86. Tarball string `json:"tarball"`
  87. FileCount int `json:"fileCount,omitempty"`
  88. UnpackedSize int `json:"unpackedSize,omitempty"`
  89. NpmSignature string `json:"npm-signature,omitempty"`
  90. }
  91. type PackageSearch struct {
  92. Objects []*PackageSearchObject `json:"objects"`
  93. Total int64 `json:"total"`
  94. }
  95. type PackageSearchObject struct {
  96. Package *PackageSearchPackage `json:"package"`
  97. }
  98. type PackageSearchPackage struct {
  99. Scope string `json:"scope"`
  100. Name string `json:"name"`
  101. Version string `json:"version"`
  102. Date time.Time `json:"date"`
  103. Description string `json:"description"`
  104. Author User `json:"author"`
  105. Publisher User `json:"publisher"`
  106. Maintainers []User `json:"maintainers"`
  107. Keywords []string `json:"keywords,omitempty"`
  108. Links *PackageSearchPackageLinks `json:"links"`
  109. }
  110. type PackageSearchPackageLinks struct {
  111. Registry string `json:"npm"`
  112. Homepage string `json:"homepage,omitempty"`
  113. Repository string `json:"repository,omitempty"`
  114. }
  115. // User https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#package
  116. type User struct {
  117. Username string `json:"username,omitempty"`
  118. Name string `json:"name"`
  119. Email string `json:"email,omitempty"`
  120. URL string `json:"url,omitempty"`
  121. }
  122. // UnmarshalJSON is needed because User objects can be strings or objects
  123. func (u *User) UnmarshalJSON(data []byte) error {
  124. switch data[0] {
  125. case '"':
  126. if err := json.Unmarshal(data, &u.Name); err != nil {
  127. return err
  128. }
  129. case '{':
  130. var tmp struct {
  131. Username string `json:"username"`
  132. Name string `json:"name"`
  133. Email string `json:"email"`
  134. URL string `json:"url"`
  135. }
  136. if err := json.Unmarshal(data, &tmp); err != nil {
  137. return err
  138. }
  139. u.Username = tmp.Username
  140. u.Name = tmp.Name
  141. u.Email = tmp.Email
  142. u.URL = tmp.URL
  143. }
  144. return nil
  145. }
  146. // Repository https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version
  147. type Repository struct {
  148. Type string `json:"type"`
  149. URL string `json:"url"`
  150. }
  151. // PackageAttachment https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#package
  152. type PackageAttachment struct {
  153. ContentType string `json:"content_type"`
  154. Data string `json:"data"`
  155. Length int `json:"length"`
  156. }
  157. type packageUpload struct {
  158. PackageMetadata
  159. Attachments map[string]*PackageAttachment `json:"_attachments"`
  160. }
  161. // ParsePackage parses the content into a npm package
  162. func ParsePackage(r io.Reader) (*Package, error) {
  163. var upload packageUpload
  164. if err := json.NewDecoder(r).Decode(&upload); err != nil {
  165. return nil, err
  166. }
  167. for _, meta := range upload.Versions {
  168. if !validateName(meta.Name) {
  169. return nil, ErrInvalidPackageName
  170. }
  171. v, err := version.NewSemver(meta.Version)
  172. if err != nil {
  173. return nil, ErrInvalidPackageVersion
  174. }
  175. scope := ""
  176. name := meta.Name
  177. nameParts := strings.SplitN(meta.Name, "/", 2)
  178. if len(nameParts) == 2 {
  179. scope = nameParts[0]
  180. name = nameParts[1]
  181. }
  182. if !validation.IsValidURL(meta.Homepage) {
  183. meta.Homepage = ""
  184. }
  185. p := &Package{
  186. Name: meta.Name,
  187. Version: v.String(),
  188. DistTags: make([]string, 0, 1),
  189. Metadata: Metadata{
  190. Scope: scope,
  191. Name: name,
  192. Description: meta.Description,
  193. Author: meta.Author.Name,
  194. License: meta.License,
  195. ProjectURL: meta.Homepage,
  196. Keywords: meta.Keywords,
  197. Dependencies: meta.Dependencies,
  198. BundleDependencies: meta.BundleDependencies,
  199. DevelopmentDependencies: meta.DevDependencies,
  200. PeerDependencies: meta.PeerDependencies,
  201. PeerDependenciesMeta: meta.PeerDependenciesMeta,
  202. OptionalDependencies: meta.OptionalDependencies,
  203. Bin: meta.Bin,
  204. Readme: meta.Readme,
  205. Repository: meta.Repository,
  206. },
  207. }
  208. for tag := range upload.DistTags {
  209. p.DistTags = append(p.DistTags, tag)
  210. }
  211. p.Filename = strings.ToLower(fmt.Sprintf("%s-%s.tgz", name, p.Version))
  212. attachment := func() *PackageAttachment {
  213. for _, a := range upload.Attachments {
  214. return a
  215. }
  216. return nil
  217. }()
  218. if attachment == nil || len(attachment.Data) == 0 {
  219. return nil, ErrInvalidAttachment
  220. }
  221. data, err := base64.StdEncoding.DecodeString(attachment.Data)
  222. if err != nil {
  223. return nil, ErrInvalidAttachment
  224. }
  225. p.Data = data
  226. integrity := strings.SplitN(meta.Dist.Integrity, "-", 2)
  227. if len(integrity) != 2 {
  228. return nil, ErrInvalidIntegrity
  229. }
  230. integrityHash, err := base64.StdEncoding.DecodeString(integrity[1])
  231. if err != nil {
  232. return nil, ErrInvalidIntegrity
  233. }
  234. var hash []byte
  235. switch integrity[0] {
  236. case "sha1":
  237. tmp := sha1.Sum(data)
  238. hash = tmp[:]
  239. case "sha512":
  240. tmp := sha512.Sum512(data)
  241. hash = tmp[:]
  242. }
  243. if !bytes.Equal(integrityHash, hash) {
  244. return nil, ErrInvalidIntegrity
  245. }
  246. return p, nil
  247. }
  248. return nil, ErrInvalidPackage
  249. }
  250. func validateName(name string) bool {
  251. if strings.TrimSpace(name) != name {
  252. return false
  253. }
  254. if len(name) == 0 || len(name) > 214 {
  255. return false
  256. }
  257. return nameMatch.MatchString(name)
  258. }