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.

cargo.go 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package cargo
  4. import (
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "strconv"
  9. "strings"
  10. "code.gitea.io/gitea/models/db"
  11. packages_model "code.gitea.io/gitea/models/packages"
  12. "code.gitea.io/gitea/modules/context"
  13. "code.gitea.io/gitea/modules/log"
  14. packages_module "code.gitea.io/gitea/modules/packages"
  15. cargo_module "code.gitea.io/gitea/modules/packages/cargo"
  16. "code.gitea.io/gitea/modules/setting"
  17. "code.gitea.io/gitea/modules/structs"
  18. "code.gitea.io/gitea/modules/util"
  19. "code.gitea.io/gitea/routers/api/packages/helper"
  20. "code.gitea.io/gitea/services/convert"
  21. packages_service "code.gitea.io/gitea/services/packages"
  22. cargo_service "code.gitea.io/gitea/services/packages/cargo"
  23. )
  24. // https://doc.rust-lang.org/cargo/reference/registries.html#web-api
  25. type StatusResponse struct {
  26. OK bool `json:"ok"`
  27. Errors []StatusMessage `json:"errors,omitempty"`
  28. }
  29. type StatusMessage struct {
  30. Message string `json:"detail"`
  31. }
  32. func apiError(ctx *context.Context, status int, obj any) {
  33. helper.LogAndProcessError(ctx, status, obj, func(message string) {
  34. ctx.JSON(status, StatusResponse{
  35. OK: false,
  36. Errors: []StatusMessage{
  37. {
  38. Message: message,
  39. },
  40. },
  41. })
  42. })
  43. }
  44. // https://rust-lang.github.io/rfcs/2789-sparse-index.html
  45. func RepositoryConfig(ctx *context.Context) {
  46. ctx.JSON(http.StatusOK, cargo_service.BuildConfig(ctx.Package.Owner, setting.Service.RequireSignInView || ctx.Package.Owner.Visibility != structs.VisibleTypePublic))
  47. }
  48. func EnumeratePackageVersions(ctx *context.Context) {
  49. p, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.TypeCargo, ctx.Params("package"))
  50. if err != nil {
  51. if errors.Is(err, util.ErrNotExist) {
  52. apiError(ctx, http.StatusNotFound, err)
  53. } else {
  54. apiError(ctx, http.StatusInternalServerError, err)
  55. }
  56. return
  57. }
  58. b, err := cargo_service.BuildPackageIndex(ctx, p)
  59. if err != nil {
  60. apiError(ctx, http.StatusInternalServerError, err)
  61. return
  62. }
  63. if b == nil {
  64. apiError(ctx, http.StatusNotFound, nil)
  65. return
  66. }
  67. ctx.PlainTextBytes(http.StatusOK, b.Bytes())
  68. }
  69. type SearchResult struct {
  70. Crates []*SearchResultCrate `json:"crates"`
  71. Meta SearchResultMeta `json:"meta"`
  72. }
  73. type SearchResultCrate struct {
  74. Name string `json:"name"`
  75. LatestVersion string `json:"max_version"`
  76. Description string `json:"description"`
  77. }
  78. type SearchResultMeta struct {
  79. Total int64 `json:"total"`
  80. }
  81. // https://doc.rust-lang.org/cargo/reference/registries.html#search
  82. func SearchPackages(ctx *context.Context) {
  83. page := ctx.FormInt("page")
  84. if page < 1 {
  85. page = 1
  86. }
  87. perPage := ctx.FormInt("per_page")
  88. paginator := db.ListOptions{
  89. Page: page,
  90. PageSize: convert.ToCorrectPageSize(perPage),
  91. }
  92. pvs, total, err := packages_model.SearchLatestVersions(
  93. ctx,
  94. &packages_model.PackageSearchOptions{
  95. OwnerID: ctx.Package.Owner.ID,
  96. Type: packages_model.TypeCargo,
  97. Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
  98. IsInternal: util.OptionalBoolFalse,
  99. Paginator: &paginator,
  100. },
  101. )
  102. if err != nil {
  103. apiError(ctx, http.StatusInternalServerError, err)
  104. return
  105. }
  106. pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
  107. if err != nil {
  108. apiError(ctx, http.StatusInternalServerError, err)
  109. return
  110. }
  111. crates := make([]*SearchResultCrate, 0, len(pvs))
  112. for _, pd := range pds {
  113. crates = append(crates, &SearchResultCrate{
  114. Name: pd.Package.Name,
  115. LatestVersion: pd.Version.Version,
  116. Description: pd.Metadata.(*cargo_module.Metadata).Description,
  117. })
  118. }
  119. ctx.JSON(http.StatusOK, SearchResult{
  120. Crates: crates,
  121. Meta: SearchResultMeta{
  122. Total: total,
  123. },
  124. })
  125. }
  126. type Owners struct {
  127. Users []OwnerUser `json:"users"`
  128. }
  129. type OwnerUser struct {
  130. ID int64 `json:"id"`
  131. Login string `json:"login"`
  132. Name string `json:"name"`
  133. }
  134. // https://doc.rust-lang.org/cargo/reference/registries.html#owners-list
  135. func ListOwners(ctx *context.Context) {
  136. ctx.JSON(http.StatusOK, Owners{
  137. Users: []OwnerUser{
  138. {
  139. ID: ctx.Package.Owner.ID,
  140. Login: ctx.Package.Owner.Name,
  141. Name: ctx.Package.Owner.DisplayName(),
  142. },
  143. },
  144. })
  145. }
  146. // DownloadPackageFile serves the content of a package
  147. func DownloadPackageFile(ctx *context.Context) {
  148. s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
  149. ctx,
  150. &packages_service.PackageInfo{
  151. Owner: ctx.Package.Owner,
  152. PackageType: packages_model.TypeCargo,
  153. Name: ctx.Params("package"),
  154. Version: ctx.Params("version"),
  155. },
  156. &packages_service.PackageFileInfo{
  157. Filename: strings.ToLower(fmt.Sprintf("%s-%s.crate", ctx.Params("package"), ctx.Params("version"))),
  158. },
  159. )
  160. if err != nil {
  161. if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
  162. apiError(ctx, http.StatusNotFound, err)
  163. return
  164. }
  165. apiError(ctx, http.StatusInternalServerError, err)
  166. return
  167. }
  168. helper.ServePackageFile(ctx, s, u, pf)
  169. }
  170. // https://doc.rust-lang.org/cargo/reference/registries.html#publish
  171. func UploadPackage(ctx *context.Context) {
  172. defer ctx.Req.Body.Close()
  173. cp, err := cargo_module.ParsePackage(ctx.Req.Body)
  174. if err != nil {
  175. apiError(ctx, http.StatusBadRequest, err)
  176. return
  177. }
  178. buf, err := packages_module.CreateHashedBufferFromReader(cp.Content)
  179. if err != nil {
  180. apiError(ctx, http.StatusInternalServerError, err)
  181. return
  182. }
  183. defer buf.Close()
  184. if buf.Size() != cp.ContentSize {
  185. apiError(ctx, http.StatusBadRequest, "invalid content size")
  186. return
  187. }
  188. pv, _, err := packages_service.CreatePackageAndAddFile(
  189. ctx,
  190. &packages_service.PackageCreationInfo{
  191. PackageInfo: packages_service.PackageInfo{
  192. Owner: ctx.Package.Owner,
  193. PackageType: packages_model.TypeCargo,
  194. Name: cp.Name,
  195. Version: cp.Version,
  196. },
  197. SemverCompatible: true,
  198. Creator: ctx.Doer,
  199. Metadata: cp.Metadata,
  200. VersionProperties: map[string]string{
  201. cargo_module.PropertyYanked: strconv.FormatBool(false),
  202. },
  203. },
  204. &packages_service.PackageFileCreationInfo{
  205. PackageFileInfo: packages_service.PackageFileInfo{
  206. Filename: strings.ToLower(fmt.Sprintf("%s-%s.crate", cp.Name, cp.Version)),
  207. },
  208. Creator: ctx.Doer,
  209. Data: buf,
  210. IsLead: true,
  211. },
  212. )
  213. if err != nil {
  214. switch err {
  215. case packages_model.ErrDuplicatePackageVersion:
  216. apiError(ctx, http.StatusConflict, err)
  217. case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
  218. apiError(ctx, http.StatusForbidden, err)
  219. default:
  220. apiError(ctx, http.StatusInternalServerError, err)
  221. }
  222. return
  223. }
  224. if err := cargo_service.AddOrUpdatePackageIndex(ctx, ctx.Doer, ctx.Package.Owner, pv.PackageID); err != nil {
  225. if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
  226. log.Error("Rollback creation of package version: %v", err)
  227. }
  228. apiError(ctx, http.StatusInternalServerError, err)
  229. return
  230. }
  231. ctx.JSON(http.StatusOK, StatusResponse{OK: true})
  232. }
  233. // https://doc.rust-lang.org/cargo/reference/registries.html#yank
  234. func YankPackage(ctx *context.Context) {
  235. yankPackage(ctx, true)
  236. }
  237. // https://doc.rust-lang.org/cargo/reference/registries.html#unyank
  238. func UnyankPackage(ctx *context.Context) {
  239. yankPackage(ctx, false)
  240. }
  241. func yankPackage(ctx *context.Context, yank bool) {
  242. pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeCargo, ctx.Params("package"), ctx.Params("version"))
  243. if err != nil {
  244. if err == packages_model.ErrPackageNotExist {
  245. apiError(ctx, http.StatusNotFound, err)
  246. return
  247. }
  248. apiError(ctx, http.StatusInternalServerError, err)
  249. return
  250. }
  251. pps, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, cargo_module.PropertyYanked)
  252. if err != nil {
  253. apiError(ctx, http.StatusInternalServerError, err)
  254. return
  255. }
  256. if len(pps) == 0 {
  257. apiError(ctx, http.StatusInternalServerError, "Property not found")
  258. return
  259. }
  260. pp := pps[0]
  261. pp.Value = strconv.FormatBool(yank)
  262. if err := packages_model.UpdateProperty(ctx, pp); err != nil {
  263. apiError(ctx, http.StatusInternalServerError, err)
  264. return
  265. }
  266. if err := cargo_service.AddOrUpdatePackageIndex(ctx, ctx.Doer, ctx.Package.Owner, pv.PackageID); err != nil {
  267. apiError(ctx, http.StatusInternalServerError, err)
  268. return
  269. }
  270. ctx.JSON(http.StatusOK, StatusResponse{OK: true})
  271. }