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.

swift.go 13KB

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