gitea源码

swift.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package swift
  4. import (
  5. "errors"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "regexp"
  10. "sort"
  11. "strings"
  12. packages_model "code.gitea.io/gitea/models/packages"
  13. "code.gitea.io/gitea/modules/json"
  14. "code.gitea.io/gitea/modules/log"
  15. "code.gitea.io/gitea/modules/optional"
  16. packages_module "code.gitea.io/gitea/modules/packages"
  17. swift_module "code.gitea.io/gitea/modules/packages/swift"
  18. "code.gitea.io/gitea/modules/setting"
  19. "code.gitea.io/gitea/modules/util"
  20. "code.gitea.io/gitea/routers/api/packages/helper"
  21. "code.gitea.io/gitea/services/context"
  22. packages_service "code.gitea.io/gitea/services/packages"
  23. "github.com/hashicorp/go-version"
  24. )
  25. // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#35-api-versioning
  26. const (
  27. AcceptJSON = "application/vnd.swift.registry.v1+json"
  28. AcceptSwift = "application/vnd.swift.registry.v1+swift"
  29. AcceptZip = "application/vnd.swift.registry.v1+zip"
  30. )
  31. var (
  32. // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#361-package-scope
  33. scopePattern = regexp.MustCompile(`\A[a-zA-Z0-9][a-zA-Z0-9-]{0,38}\z`)
  34. // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#362-package-name
  35. namePattern = regexp.MustCompile(`\A[a-zA-Z0-9][a-zA-Z0-9-_]{0,99}\z`)
  36. )
  37. type headers struct {
  38. Status int
  39. ContentType string
  40. Digest string
  41. Location string
  42. Link string
  43. }
  44. // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#35-api-versioning
  45. func setResponseHeaders(resp http.ResponseWriter, h *headers) {
  46. if h.ContentType != "" {
  47. resp.Header().Set("Content-Type", h.ContentType)
  48. }
  49. if h.Digest != "" {
  50. resp.Header().Set("Digest", "sha256="+h.Digest)
  51. }
  52. if h.Location != "" {
  53. resp.Header().Set("Location", h.Location)
  54. }
  55. if h.Link != "" {
  56. resp.Header().Set("Link", h.Link)
  57. }
  58. resp.Header().Set("Content-Version", "1")
  59. if h.Status != 0 {
  60. resp.WriteHeader(h.Status)
  61. }
  62. }
  63. // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#33-error-handling
  64. func apiError(ctx *context.Context, status int, obj any) {
  65. // https://www.rfc-editor.org/rfc/rfc7807
  66. type Problem struct {
  67. Status int `json:"status"`
  68. Detail string `json:"detail"`
  69. }
  70. message := helper.ProcessErrorForUser(ctx, status, obj)
  71. setResponseHeaders(ctx.Resp, &headers{
  72. Status: status,
  73. ContentType: "application/problem+json",
  74. })
  75. _ = json.NewEncoder(ctx.Resp).Encode(Problem{
  76. Status: status,
  77. Detail: message,
  78. })
  79. }
  80. // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#35-api-versioning
  81. func CheckAcceptMediaType(requiredAcceptHeader string) func(ctx *context.Context) {
  82. return func(ctx *context.Context) {
  83. accept := ctx.Req.Header.Get("Accept")
  84. if accept != "" && accept != requiredAcceptHeader {
  85. apiError(ctx, http.StatusBadRequest, fmt.Sprintf("Unexpected accept header. Should be '%s'.", requiredAcceptHeader))
  86. }
  87. }
  88. }
  89. // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/PackageRegistryUsage.md#registry-authentication
  90. func CheckAuthenticate(ctx *context.Context) {
  91. if ctx.Doer == nil {
  92. apiError(ctx, http.StatusUnauthorized, nil)
  93. return
  94. }
  95. ctx.Status(http.StatusOK)
  96. }
  97. func buildPackageID(scope, name string) string {
  98. return scope + "." + name
  99. }
  100. type Release struct {
  101. URL string `json:"url"`
  102. }
  103. type EnumeratePackageVersionsResponse struct {
  104. Releases map[string]Release `json:"releases"`
  105. }
  106. // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#41-list-package-releases
  107. func EnumeratePackageVersions(ctx *context.Context) {
  108. packageScope := ctx.PathParam("scope")
  109. packageName := ctx.PathParam("name")
  110. pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeSwift, buildPackageID(packageScope, packageName))
  111. if err != nil {
  112. apiError(ctx, http.StatusInternalServerError, err)
  113. return
  114. }
  115. if len(pvs) == 0 {
  116. apiError(ctx, http.StatusNotFound, nil)
  117. return
  118. }
  119. pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
  120. if err != nil {
  121. apiError(ctx, http.StatusInternalServerError, err)
  122. return
  123. }
  124. sort.Slice(pds, func(i, j int) bool {
  125. return pds[i].SemVer.LessThan(pds[j].SemVer)
  126. })
  127. baseURL := fmt.Sprintf("%sapi/packages/%s/swift/%s/%s/", setting.AppURL, ctx.Package.Owner.LowerName, packageScope, packageName)
  128. releases := make(map[string]Release)
  129. for _, pd := range pds {
  130. version := pd.SemVer.String()
  131. releases[version] = Release{
  132. URL: baseURL + version,
  133. }
  134. }
  135. setResponseHeaders(ctx.Resp, &headers{
  136. Link: fmt.Sprintf(`<%s%s>; rel="latest-version"`, baseURL, pds[len(pds)-1].Version.Version),
  137. })
  138. ctx.JSON(http.StatusOK, EnumeratePackageVersionsResponse{
  139. Releases: releases,
  140. })
  141. }
  142. type Resource struct {
  143. Name string `json:"name"`
  144. Type string `json:"type"`
  145. Checksum string `json:"checksum"`
  146. }
  147. type PackageVersionMetadataResponse struct {
  148. ID string `json:"id"`
  149. Version string `json:"version"`
  150. Resources []Resource `json:"resources"`
  151. Metadata *swift_module.SoftwareSourceCode `json:"metadata"`
  152. }
  153. // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-2
  154. func PackageVersionMetadata(ctx *context.Context) {
  155. id := buildPackageID(ctx.PathParam("scope"), ctx.PathParam("name"))
  156. pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeSwift, id, ctx.PathParam("version"))
  157. if err != nil {
  158. if errors.Is(err, util.ErrNotExist) {
  159. apiError(ctx, http.StatusNotFound, err)
  160. } else {
  161. apiError(ctx, http.StatusInternalServerError, err)
  162. }
  163. return
  164. }
  165. pd, err := packages_model.GetPackageDescriptor(ctx, pv)
  166. if err != nil {
  167. apiError(ctx, http.StatusInternalServerError, err)
  168. return
  169. }
  170. metadata := pd.Metadata.(*swift_module.Metadata)
  171. setResponseHeaders(ctx.Resp, &headers{})
  172. ctx.JSON(http.StatusOK, PackageVersionMetadataResponse{
  173. ID: id,
  174. Version: pd.Version.Version,
  175. Resources: []Resource{
  176. {
  177. Name: "source-archive",
  178. Type: "application/zip",
  179. Checksum: pd.Files[0].Blob.HashSHA256,
  180. },
  181. },
  182. Metadata: &swift_module.SoftwareSourceCode{
  183. Context: []string{"http://schema.org/"},
  184. Type: "SoftwareSourceCode",
  185. Name: pd.PackageProperties.GetByName(swift_module.PropertyName),
  186. Version: pd.Version.Version,
  187. Description: metadata.Description,
  188. Keywords: metadata.Keywords,
  189. CodeRepository: metadata.RepositoryURL,
  190. License: metadata.License,
  191. ProgrammingLanguage: swift_module.ProgrammingLanguage{
  192. Type: "ComputerLanguage",
  193. Name: "Swift",
  194. URL: "https://swift.org",
  195. },
  196. Author: swift_module.Person{
  197. Type: "Person",
  198. Name: metadata.Author.String(),
  199. GivenName: metadata.Author.GivenName,
  200. MiddleName: metadata.Author.MiddleName,
  201. FamilyName: metadata.Author.FamilyName,
  202. },
  203. },
  204. })
  205. }
  206. // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#43-fetch-manifest-for-a-package-release
  207. func DownloadManifest(ctx *context.Context) {
  208. packageScope := ctx.PathParam("scope")
  209. packageName := ctx.PathParam("name")
  210. packageVersion := ctx.PathParam("version")
  211. pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeSwift, buildPackageID(packageScope, packageName), packageVersion)
  212. if err != nil {
  213. if errors.Is(err, util.ErrNotExist) {
  214. apiError(ctx, http.StatusNotFound, err)
  215. } else {
  216. apiError(ctx, http.StatusInternalServerError, err)
  217. }
  218. return
  219. }
  220. pd, err := packages_model.GetPackageDescriptor(ctx, pv)
  221. if err != nil {
  222. apiError(ctx, http.StatusInternalServerError, err)
  223. return
  224. }
  225. swiftVersion := ctx.FormTrim("swift-version")
  226. if swiftVersion != "" {
  227. v, err := version.NewVersion(swiftVersion)
  228. if err == nil {
  229. swiftVersion = swift_module.TrimmedVersionString(v)
  230. }
  231. }
  232. m, ok := pd.Metadata.(*swift_module.Metadata).Manifests[swiftVersion]
  233. if !ok {
  234. setResponseHeaders(ctx.Resp, &headers{
  235. Status: http.StatusSeeOther,
  236. Location: fmt.Sprintf("%sapi/packages/%s/swift/%s/%s/%s/Package.swift", setting.AppURL, ctx.Package.Owner.LowerName, packageScope, packageName, packageVersion),
  237. })
  238. return
  239. }
  240. setResponseHeaders(ctx.Resp, &headers{})
  241. filename := "Package.swift"
  242. if swiftVersion != "" {
  243. filename = fmt.Sprintf("Package@swift-%s.swift", swiftVersion)
  244. }
  245. ctx.ServeContent(strings.NewReader(m.Content), &context.ServeHeaderOptions{
  246. ContentType: "text/x-swift",
  247. Filename: filename,
  248. LastModified: pv.CreatedUnix.AsLocalTime(),
  249. })
  250. }
  251. // formFileOptionalReadCloser returns (nil, nil) if the formKey is not present.
  252. func formFileOptionalReadCloser(ctx *context.Context, formKey string) (io.ReadCloser, error) {
  253. multipartFile, _, err := ctx.Req.FormFile(formKey)
  254. if err != nil && !errors.Is(err, http.ErrMissingFile) {
  255. return nil, err
  256. }
  257. if multipartFile != nil {
  258. return multipartFile, nil
  259. }
  260. content := ctx.Req.FormValue(formKey)
  261. if content == "" {
  262. return nil, nil
  263. }
  264. return io.NopCloser(strings.NewReader(content)), nil
  265. }
  266. // UploadPackageFile refers to https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-6
  267. func UploadPackageFile(ctx *context.Context) {
  268. packageScope := ctx.PathParam("scope")
  269. packageName := ctx.PathParam("name")
  270. v, err := version.NewVersion(ctx.PathParam("version"))
  271. if !scopePattern.MatchString(packageScope) || !namePattern.MatchString(packageName) || err != nil {
  272. apiError(ctx, http.StatusBadRequest, err)
  273. return
  274. }
  275. packageVersion := v.Core().String()
  276. file, err := formFileOptionalReadCloser(ctx, "source-archive")
  277. if file == nil || err != nil {
  278. apiError(ctx, http.StatusBadRequest, "unable to read source-archive file")
  279. return
  280. }
  281. defer file.Close()
  282. buf, err := packages_module.CreateHashedBufferFromReader(file)
  283. if err != nil {
  284. apiError(ctx, http.StatusInternalServerError, err)
  285. return
  286. }
  287. defer buf.Close()
  288. mr, err := formFileOptionalReadCloser(ctx, "metadata")
  289. if err != nil {
  290. apiError(ctx, http.StatusBadRequest, "unable to read metadata file")
  291. return
  292. }
  293. if mr != nil {
  294. defer mr.Close()
  295. }
  296. pck, err := swift_module.ParsePackage(buf, buf.Size(), mr)
  297. if err != nil {
  298. if errors.Is(err, util.ErrInvalidArgument) {
  299. apiError(ctx, http.StatusBadRequest, err)
  300. } else {
  301. apiError(ctx, http.StatusInternalServerError, err)
  302. }
  303. return
  304. }
  305. if _, err := buf.Seek(0, io.SeekStart); err != nil {
  306. apiError(ctx, http.StatusInternalServerError, err)
  307. return
  308. }
  309. pv, _, err := packages_service.CreatePackageAndAddFile(
  310. ctx,
  311. &packages_service.PackageCreationInfo{
  312. PackageInfo: packages_service.PackageInfo{
  313. Owner: ctx.Package.Owner,
  314. PackageType: packages_model.TypeSwift,
  315. Name: buildPackageID(packageScope, packageName),
  316. Version: packageVersion,
  317. },
  318. SemverCompatible: true,
  319. Creator: ctx.Doer,
  320. Metadata: pck.Metadata,
  321. PackageProperties: map[string]string{
  322. swift_module.PropertyScope: packageScope,
  323. swift_module.PropertyName: packageName,
  324. },
  325. },
  326. &packages_service.PackageFileCreationInfo{
  327. PackageFileInfo: packages_service.PackageFileInfo{
  328. Filename: fmt.Sprintf("%s-%s.zip", packageName, packageVersion),
  329. },
  330. Creator: ctx.Doer,
  331. Data: buf,
  332. IsLead: true,
  333. },
  334. )
  335. if err != nil {
  336. switch err {
  337. case packages_model.ErrDuplicatePackageVersion:
  338. apiError(ctx, http.StatusConflict, err)
  339. case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
  340. apiError(ctx, http.StatusForbidden, err)
  341. default:
  342. apiError(ctx, http.StatusInternalServerError, err)
  343. }
  344. return
  345. }
  346. for _, url := range pck.RepositoryURLs {
  347. _, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, swift_module.PropertyRepositoryURL, url)
  348. if err != nil {
  349. log.Error("InsertProperty failed: %v", err)
  350. }
  351. }
  352. setResponseHeaders(ctx.Resp, &headers{})
  353. ctx.Status(http.StatusCreated)
  354. }
  355. // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-4
  356. func DownloadPackageFile(ctx *context.Context) {
  357. pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeSwift, buildPackageID(ctx.PathParam("scope"), ctx.PathParam("name")), ctx.PathParam("version"))
  358. if err != nil {
  359. if errors.Is(err, util.ErrNotExist) {
  360. apiError(ctx, http.StatusNotFound, err)
  361. } else {
  362. apiError(ctx, http.StatusInternalServerError, err)
  363. }
  364. return
  365. }
  366. pd, err := packages_model.GetPackageDescriptor(ctx, pv)
  367. if err != nil {
  368. apiError(ctx, http.StatusInternalServerError, err)
  369. return
  370. }
  371. pf := pd.Files[0].File
  372. s, u, _, err := packages_service.OpenFileForDownload(ctx, pf, ctx.Req.Method)
  373. if err != nil {
  374. apiError(ctx, http.StatusInternalServerError, err)
  375. return
  376. }
  377. setResponseHeaders(ctx.Resp, &headers{
  378. Digest: pd.Files[0].Blob.HashSHA256,
  379. })
  380. helper.ServePackageFile(ctx, s, u, pf, &context.ServeHeaderOptions{
  381. Filename: pf.Name,
  382. ContentType: "application/zip",
  383. LastModified: pf.CreatedUnix.AsLocalTime(),
  384. })
  385. }
  386. type LookupPackageIdentifiersResponse struct {
  387. Identifiers []string `json:"identifiers"`
  388. }
  389. // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-5
  390. func LookupPackageIdentifiers(ctx *context.Context) {
  391. url := ctx.FormTrim("url")
  392. if url == "" {
  393. apiError(ctx, http.StatusBadRequest, nil)
  394. return
  395. }
  396. pvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
  397. OwnerID: ctx.Package.Owner.ID,
  398. Type: packages_model.TypeSwift,
  399. Properties: map[string]string{
  400. swift_module.PropertyRepositoryURL: url,
  401. },
  402. IsInternal: optional.Some(false),
  403. })
  404. if err != nil {
  405. apiError(ctx, http.StatusInternalServerError, err)
  406. return
  407. }
  408. if len(pvs) == 0 {
  409. apiError(ctx, http.StatusNotFound, nil)
  410. return
  411. }
  412. pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
  413. if err != nil {
  414. apiError(ctx, http.StatusInternalServerError, err)
  415. return
  416. }
  417. identifiers := make([]string, 0, len(pds))
  418. for _, pd := range pds {
  419. identifiers = append(identifiers, pd.Package.Name)
  420. }
  421. setResponseHeaders(ctx.Resp, &headers{})
  422. ctx.JSON(http.StatusOK, LookupPackageIdentifiersResponse{
  423. Identifiers: identifiers,
  424. })
  425. }