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.

api.go 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package packages
  4. import (
  5. gocontext "context"
  6. "net/http"
  7. "regexp"
  8. "strings"
  9. "code.gitea.io/gitea/models/perm"
  10. "code.gitea.io/gitea/modules/context"
  11. "code.gitea.io/gitea/modules/log"
  12. "code.gitea.io/gitea/modules/setting"
  13. "code.gitea.io/gitea/modules/web"
  14. "code.gitea.io/gitea/routers/api/packages/composer"
  15. "code.gitea.io/gitea/routers/api/packages/conan"
  16. "code.gitea.io/gitea/routers/api/packages/conda"
  17. "code.gitea.io/gitea/routers/api/packages/container"
  18. "code.gitea.io/gitea/routers/api/packages/generic"
  19. "code.gitea.io/gitea/routers/api/packages/helm"
  20. "code.gitea.io/gitea/routers/api/packages/maven"
  21. "code.gitea.io/gitea/routers/api/packages/npm"
  22. "code.gitea.io/gitea/routers/api/packages/nuget"
  23. "code.gitea.io/gitea/routers/api/packages/pub"
  24. "code.gitea.io/gitea/routers/api/packages/pypi"
  25. "code.gitea.io/gitea/routers/api/packages/rubygems"
  26. "code.gitea.io/gitea/routers/api/packages/vagrant"
  27. "code.gitea.io/gitea/services/auth"
  28. context_service "code.gitea.io/gitea/services/context"
  29. )
  30. func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
  31. return func(ctx *context.Context) {
  32. if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
  33. ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea Package API"`)
  34. ctx.Error(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin")
  35. return
  36. }
  37. }
  38. }
  39. // CommonRoutes provide endpoints for most package managers (except containers - see below)
  40. // These are mounted on `/api/packages` (not `/api/v1/packages`)
  41. func CommonRoutes(ctx gocontext.Context) *web.Route {
  42. r := web.NewRoute()
  43. r.Use(context.PackageContexter(ctx))
  44. authMethods := []auth.Method{
  45. &auth.OAuth2{},
  46. &auth.Basic{},
  47. &nuget.Auth{},
  48. &conan.Auth{},
  49. }
  50. if setting.Service.EnableReverseProxyAuth {
  51. authMethods = append(authMethods, &auth.ReverseProxy{})
  52. }
  53. authGroup := auth.NewGroup(authMethods...)
  54. r.Use(func(ctx *context.Context) {
  55. var err error
  56. ctx.Doer, err = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
  57. if err != nil {
  58. log.Error("Verify: %v", err)
  59. ctx.Error(http.StatusUnauthorized, "authGroup.Verify")
  60. return
  61. }
  62. ctx.IsSigned = ctx.Doer != nil
  63. })
  64. r.Group("/{username}", func() {
  65. r.Group("/composer", func() {
  66. r.Get("/packages.json", composer.ServiceIndex)
  67. r.Get("/search.json", composer.SearchPackages)
  68. r.Get("/list.json", composer.EnumeratePackages)
  69. r.Get("/p2/{vendorname}/{projectname}~dev.json", composer.PackageMetadata)
  70. r.Get("/p2/{vendorname}/{projectname}.json", composer.PackageMetadata)
  71. r.Get("/files/{package}/{version}/{filename}", composer.DownloadPackageFile)
  72. r.Put("", reqPackageAccess(perm.AccessModeWrite), composer.UploadPackage)
  73. }, reqPackageAccess(perm.AccessModeRead))
  74. r.Group("/conan", func() {
  75. r.Group("/v1", func() {
  76. r.Get("/ping", conan.Ping)
  77. r.Group("/users", func() {
  78. r.Get("/authenticate", conan.Authenticate)
  79. r.Get("/check_credentials", conan.CheckCredentials)
  80. })
  81. r.Group("/conans", func() {
  82. r.Get("/search", conan.SearchRecipes)
  83. r.Group("/{name}/{version}/{user}/{channel}", func() {
  84. r.Get("", conan.RecipeSnapshot)
  85. r.Delete("", reqPackageAccess(perm.AccessModeWrite), conan.DeleteRecipeV1)
  86. r.Get("/search", conan.SearchPackagesV1)
  87. r.Get("/digest", conan.RecipeDownloadURLs)
  88. r.Post("/upload_urls", reqPackageAccess(perm.AccessModeWrite), conan.RecipeUploadURLs)
  89. r.Get("/download_urls", conan.RecipeDownloadURLs)
  90. r.Group("/packages", func() {
  91. r.Post("/delete", reqPackageAccess(perm.AccessModeWrite), conan.DeletePackageV1)
  92. r.Group("/{package_reference}", func() {
  93. r.Get("", conan.PackageSnapshot)
  94. r.Get("/digest", conan.PackageDownloadURLs)
  95. r.Post("/upload_urls", reqPackageAccess(perm.AccessModeWrite), conan.PackageUploadURLs)
  96. r.Get("/download_urls", conan.PackageDownloadURLs)
  97. })
  98. })
  99. }, conan.ExtractPathParameters)
  100. })
  101. r.Group("/files/{name}/{version}/{user}/{channel}/{recipe_revision}", func() {
  102. r.Group("/recipe/{filename}", func() {
  103. r.Get("", conan.DownloadRecipeFile)
  104. r.Put("", reqPackageAccess(perm.AccessModeWrite), conan.UploadRecipeFile)
  105. })
  106. r.Group("/package/{package_reference}/{package_revision}/{filename}", func() {
  107. r.Get("", conan.DownloadPackageFile)
  108. r.Put("", reqPackageAccess(perm.AccessModeWrite), conan.UploadPackageFile)
  109. })
  110. }, conan.ExtractPathParameters)
  111. })
  112. r.Group("/v2", func() {
  113. r.Get("/ping", conan.Ping)
  114. r.Group("/users", func() {
  115. r.Get("/authenticate", conan.Authenticate)
  116. r.Get("/check_credentials", conan.CheckCredentials)
  117. })
  118. r.Group("/conans", func() {
  119. r.Get("/search", conan.SearchRecipes)
  120. r.Group("/{name}/{version}/{user}/{channel}", func() {
  121. r.Delete("", reqPackageAccess(perm.AccessModeWrite), conan.DeleteRecipeV2)
  122. r.Get("/search", conan.SearchPackagesV2)
  123. r.Get("/latest", conan.LatestRecipeRevision)
  124. r.Group("/revisions", func() {
  125. r.Get("", conan.ListRecipeRevisions)
  126. r.Group("/{recipe_revision}", func() {
  127. r.Delete("", reqPackageAccess(perm.AccessModeWrite), conan.DeleteRecipeV2)
  128. r.Get("/search", conan.SearchPackagesV2)
  129. r.Group("/files", func() {
  130. r.Get("", conan.ListRecipeRevisionFiles)
  131. r.Group("/{filename}", func() {
  132. r.Get("", conan.DownloadRecipeFile)
  133. r.Put("", reqPackageAccess(perm.AccessModeWrite), conan.UploadRecipeFile)
  134. })
  135. })
  136. r.Group("/packages", func() {
  137. r.Delete("", reqPackageAccess(perm.AccessModeWrite), conan.DeletePackageV2)
  138. r.Group("/{package_reference}", func() {
  139. r.Delete("", reqPackageAccess(perm.AccessModeWrite), conan.DeletePackageV2)
  140. r.Get("/latest", conan.LatestPackageRevision)
  141. r.Group("/revisions", func() {
  142. r.Get("", conan.ListPackageRevisions)
  143. r.Group("/{package_revision}", func() {
  144. r.Delete("", reqPackageAccess(perm.AccessModeWrite), conan.DeletePackageV2)
  145. r.Group("/files", func() {
  146. r.Get("", conan.ListPackageRevisionFiles)
  147. r.Group("/{filename}", func() {
  148. r.Get("", conan.DownloadPackageFile)
  149. r.Put("", reqPackageAccess(perm.AccessModeWrite), conan.UploadPackageFile)
  150. })
  151. })
  152. })
  153. })
  154. })
  155. })
  156. })
  157. })
  158. }, conan.ExtractPathParameters)
  159. })
  160. })
  161. }, reqPackageAccess(perm.AccessModeRead))
  162. r.Group("/conda", func() {
  163. var (
  164. downloadPattern = regexp.MustCompile(`\A(.+/)?(.+)/((?:[^/]+(?:\.tar\.bz2|\.conda))|(?:current_)?repodata\.json(?:\.bz2)?)\z`)
  165. uploadPattern = regexp.MustCompile(`\A(.+/)?([^/]+(?:\.tar\.bz2|\.conda))\z`)
  166. )
  167. r.Get("/*", func(ctx *context.Context) {
  168. m := downloadPattern.FindStringSubmatch(ctx.Params("*"))
  169. if len(m) == 0 {
  170. ctx.Status(http.StatusNotFound)
  171. return
  172. }
  173. ctx.SetParams("channel", strings.TrimSuffix(m[1], "/"))
  174. ctx.SetParams("architecture", m[2])
  175. ctx.SetParams("filename", m[3])
  176. switch m[3] {
  177. case "repodata.json", "repodata.json.bz2", "current_repodata.json", "current_repodata.json.bz2":
  178. conda.EnumeratePackages(ctx)
  179. default:
  180. conda.DownloadPackageFile(ctx)
  181. }
  182. })
  183. r.Put("/*", reqPackageAccess(perm.AccessModeWrite), func(ctx *context.Context) {
  184. m := uploadPattern.FindStringSubmatch(ctx.Params("*"))
  185. if len(m) == 0 {
  186. ctx.Status(http.StatusNotFound)
  187. return
  188. }
  189. ctx.SetParams("channel", strings.TrimSuffix(m[1], "/"))
  190. ctx.SetParams("filename", m[2])
  191. conda.UploadPackageFile(ctx)
  192. })
  193. }, reqPackageAccess(perm.AccessModeRead))
  194. r.Group("/generic", func() {
  195. r.Group("/{packagename}/{packageversion}", func() {
  196. r.Delete("", reqPackageAccess(perm.AccessModeWrite), generic.DeletePackage)
  197. r.Group("/{filename}", func() {
  198. r.Get("", generic.DownloadPackageFile)
  199. r.Group("", func() {
  200. r.Put("", generic.UploadPackage)
  201. r.Delete("", generic.DeletePackageFile)
  202. }, reqPackageAccess(perm.AccessModeWrite))
  203. })
  204. })
  205. }, reqPackageAccess(perm.AccessModeRead))
  206. r.Group("/helm", func() {
  207. r.Get("/index.yaml", helm.Index)
  208. r.Get("/{filename}", helm.DownloadPackageFile)
  209. r.Post("/api/charts", reqPackageAccess(perm.AccessModeWrite), helm.UploadPackage)
  210. }, reqPackageAccess(perm.AccessModeRead))
  211. r.Group("/maven", func() {
  212. r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile)
  213. r.Get("/*", maven.DownloadPackageFile)
  214. r.Head("/*", maven.ProvidePackageFileHeader)
  215. }, reqPackageAccess(perm.AccessModeRead))
  216. r.Group("/nuget", func() {
  217. r.Group("", func() { // Needs to be unauthenticated for the NuGet client.
  218. r.Get("/", nuget.ServiceIndexV2)
  219. r.Get("/index.json", nuget.ServiceIndexV3)
  220. r.Get("/$metadata", nuget.FeedCapabilityResource)
  221. })
  222. r.Group("", func() {
  223. r.Get("/query", nuget.SearchServiceV3)
  224. r.Group("/registration/{id}", func() {
  225. r.Get("/index.json", nuget.RegistrationIndex)
  226. r.Get("/{version}", nuget.RegistrationLeafV3)
  227. })
  228. r.Group("/package/{id}", func() {
  229. r.Get("/index.json", nuget.EnumeratePackageVersionsV3)
  230. r.Get("/{version}/{filename}", nuget.DownloadPackageFile)
  231. })
  232. r.Group("", func() {
  233. r.Put("/", nuget.UploadPackage)
  234. r.Put("/symbolpackage", nuget.UploadSymbolPackage)
  235. r.Delete("/{id}/{version}", nuget.DeletePackage)
  236. }, reqPackageAccess(perm.AccessModeWrite))
  237. r.Get("/symbols/{filename}/{guid:[0-9a-fA-F]{32}[fF]{8}}/{filename2}", nuget.DownloadSymbolFile)
  238. r.Get("/Packages(Id='{id:[^']+}',Version='{version:[^']+}')", nuget.RegistrationLeafV2)
  239. r.Get("/Packages()", nuget.SearchServiceV2)
  240. r.Get("/FindPackagesById()", nuget.EnumeratePackageVersionsV2)
  241. r.Get("/Search()", nuget.SearchServiceV2)
  242. }, reqPackageAccess(perm.AccessModeRead))
  243. })
  244. r.Group("/npm", func() {
  245. r.Group("/@{scope}/{id}", func() {
  246. r.Get("", npm.PackageMetadata)
  247. r.Put("", reqPackageAccess(perm.AccessModeWrite), npm.UploadPackage)
  248. r.Group("/-/{version}/{filename}", func() {
  249. r.Get("", npm.DownloadPackageFile)
  250. r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion)
  251. })
  252. r.Get("/-/{filename}", npm.DownloadPackageFileByName)
  253. r.Group("/-rev/{revision}", func() {
  254. r.Delete("", npm.DeletePackage)
  255. r.Put("", npm.DeletePreview)
  256. }, reqPackageAccess(perm.AccessModeWrite))
  257. })
  258. r.Group("/{id}", func() {
  259. r.Get("", npm.PackageMetadata)
  260. r.Put("", reqPackageAccess(perm.AccessModeWrite), npm.UploadPackage)
  261. r.Group("/-/{version}/{filename}", func() {
  262. r.Get("", npm.DownloadPackageFile)
  263. r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion)
  264. })
  265. r.Get("/-/{filename}", npm.DownloadPackageFileByName)
  266. r.Group("/-rev/{revision}", func() {
  267. r.Delete("", npm.DeletePackage)
  268. r.Put("", npm.DeletePreview)
  269. }, reqPackageAccess(perm.AccessModeWrite))
  270. })
  271. r.Group("/-/package/@{scope}/{id}/dist-tags", func() {
  272. r.Get("", npm.ListPackageTags)
  273. r.Group("/{tag}", func() {
  274. r.Put("", npm.AddPackageTag)
  275. r.Delete("", npm.DeletePackageTag)
  276. }, reqPackageAccess(perm.AccessModeWrite))
  277. })
  278. r.Group("/-/package/{id}/dist-tags", func() {
  279. r.Get("", npm.ListPackageTags)
  280. r.Group("/{tag}", func() {
  281. r.Put("", npm.AddPackageTag)
  282. r.Delete("", npm.DeletePackageTag)
  283. }, reqPackageAccess(perm.AccessModeWrite))
  284. })
  285. r.Group("/-/v1/search", func() {
  286. r.Get("", npm.PackageSearch)
  287. })
  288. }, reqPackageAccess(perm.AccessModeRead))
  289. r.Group("/pub", func() {
  290. r.Group("/api/packages", func() {
  291. r.Group("/versions/new", func() {
  292. r.Get("", pub.RequestUpload)
  293. r.Post("/upload", pub.UploadPackageFile)
  294. r.Get("/finalize/{id}/{version}", pub.FinalizePackage)
  295. }, reqPackageAccess(perm.AccessModeWrite))
  296. r.Group("/{id}", func() {
  297. r.Get("", pub.EnumeratePackageVersions)
  298. r.Get("/files/{version}", pub.DownloadPackageFile)
  299. r.Get("/{version}", pub.PackageVersionMetadata)
  300. })
  301. })
  302. }, reqPackageAccess(perm.AccessModeRead))
  303. r.Group("/pypi", func() {
  304. r.Post("/", reqPackageAccess(perm.AccessModeWrite), pypi.UploadPackageFile)
  305. r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
  306. r.Get("/simple/{id}", pypi.PackageMetadata)
  307. }, reqPackageAccess(perm.AccessModeRead))
  308. r.Group("/rubygems", func() {
  309. r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
  310. r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
  311. r.Get("/prerelease_specs.4.8.gz", rubygems.EnumeratePackagesPreRelease)
  312. r.Get("/quick/Marshal.4.8/{filename}", rubygems.ServePackageSpecification)
  313. r.Get("/gems/{filename}", rubygems.DownloadPackageFile)
  314. r.Group("/api/v1/gems", func() {
  315. r.Post("/", rubygems.UploadPackageFile)
  316. r.Delete("/yank", rubygems.DeletePackage)
  317. }, reqPackageAccess(perm.AccessModeWrite))
  318. }, reqPackageAccess(perm.AccessModeRead))
  319. r.Group("/vagrant", func() {
  320. r.Group("/authenticate", func() {
  321. r.Get("", vagrant.CheckAuthenticate)
  322. })
  323. r.Group("/{name}", func() {
  324. r.Head("", vagrant.CheckBoxAvailable)
  325. r.Get("", vagrant.EnumeratePackageVersions)
  326. r.Group("/{version}/{provider}", func() {
  327. r.Get("", vagrant.DownloadPackageFile)
  328. r.Put("", reqPackageAccess(perm.AccessModeWrite), vagrant.UploadPackageFile)
  329. })
  330. })
  331. }, reqPackageAccess(perm.AccessModeRead))
  332. }, context_service.UserAssignmentWeb(), context.PackageAssignment())
  333. return r
  334. }
  335. // ContainerRoutes provides endpoints that implement the OCI API to serve containers
  336. // These have to be mounted on `/v2/...` to comply with the OCI spec:
  337. // https://github.com/opencontainers/distribution-spec/blob/main/spec.md
  338. func ContainerRoutes(ctx gocontext.Context) *web.Route {
  339. r := web.NewRoute()
  340. r.Use(context.PackageContexter(ctx))
  341. authMethods := []auth.Method{
  342. &auth.Basic{},
  343. &container.Auth{},
  344. }
  345. if setting.Service.EnableReverseProxyAuth {
  346. authMethods = append(authMethods, &auth.ReverseProxy{})
  347. }
  348. authGroup := auth.NewGroup(authMethods...)
  349. r.Use(func(ctx *context.Context) {
  350. var err error
  351. ctx.Doer, err = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
  352. if err != nil {
  353. log.Error("Failed to verify user: %v", err)
  354. ctx.Error(http.StatusUnauthorized, "Verify")
  355. return
  356. }
  357. ctx.IsSigned = ctx.Doer != nil
  358. })
  359. r.Get("", container.ReqContainerAccess, container.DetermineSupport)
  360. r.Get("/token", container.Authenticate)
  361. r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList)
  362. r.Group("/{username}", func() {
  363. r.Group("/{image}", func() {
  364. r.Group("/blobs/uploads", func() {
  365. r.Post("", container.InitiateUploadBlob)
  366. r.Group("/{uuid}", func() {
  367. r.Get("", container.GetUploadBlob)
  368. r.Patch("", container.UploadBlob)
  369. r.Put("", container.EndUploadBlob)
  370. r.Delete("", container.CancelUploadBlob)
  371. })
  372. }, reqPackageAccess(perm.AccessModeWrite))
  373. r.Group("/blobs/{digest}", func() {
  374. r.Head("", container.HeadBlob)
  375. r.Get("", container.GetBlob)
  376. r.Delete("", reqPackageAccess(perm.AccessModeWrite), container.DeleteBlob)
  377. })
  378. r.Group("/manifests/{reference}", func() {
  379. r.Put("", reqPackageAccess(perm.AccessModeWrite), container.UploadManifest)
  380. r.Head("", container.HeadManifest)
  381. r.Get("", container.GetManifest)
  382. r.Delete("", reqPackageAccess(perm.AccessModeWrite), container.DeleteManifest)
  383. })
  384. r.Get("/tags/list", container.GetTagList)
  385. }, container.VerifyImageName)
  386. var (
  387. blobsUploadsPattern = regexp.MustCompile(`\A(.+)/blobs/uploads/([a-zA-Z0-9-_.=]+)\z`)
  388. blobsPattern = regexp.MustCompile(`\A(.+)/blobs/([^/]+)\z`)
  389. manifestsPattern = regexp.MustCompile(`\A(.+)/manifests/([^/]+)\z`)
  390. )
  391. // Manual mapping of routes because {image} can contain slashes which chi does not support
  392. r.Route("/*", "HEAD,GET,POST,PUT,PATCH,DELETE", func(ctx *context.Context) {
  393. path := ctx.Params("*")
  394. isHead := ctx.Req.Method == "HEAD"
  395. isGet := ctx.Req.Method == "GET"
  396. isPost := ctx.Req.Method == "POST"
  397. isPut := ctx.Req.Method == "PUT"
  398. isPatch := ctx.Req.Method == "PATCH"
  399. isDelete := ctx.Req.Method == "DELETE"
  400. if isPost && strings.HasSuffix(path, "/blobs/uploads") {
  401. reqPackageAccess(perm.AccessModeWrite)(ctx)
  402. if ctx.Written() {
  403. return
  404. }
  405. ctx.SetParams("image", path[:len(path)-14])
  406. container.VerifyImageName(ctx)
  407. if ctx.Written() {
  408. return
  409. }
  410. container.InitiateUploadBlob(ctx)
  411. return
  412. }
  413. if isGet && strings.HasSuffix(path, "/tags/list") {
  414. ctx.SetParams("image", path[:len(path)-10])
  415. container.VerifyImageName(ctx)
  416. if ctx.Written() {
  417. return
  418. }
  419. container.GetTagList(ctx)
  420. return
  421. }
  422. m := blobsUploadsPattern.FindStringSubmatch(path)
  423. if len(m) == 3 && (isGet || isPut || isPatch || isDelete) {
  424. reqPackageAccess(perm.AccessModeWrite)(ctx)
  425. if ctx.Written() {
  426. return
  427. }
  428. ctx.SetParams("image", m[1])
  429. container.VerifyImageName(ctx)
  430. if ctx.Written() {
  431. return
  432. }
  433. ctx.SetParams("uuid", m[2])
  434. if isGet {
  435. container.GetUploadBlob(ctx)
  436. } else if isPatch {
  437. container.UploadBlob(ctx)
  438. } else if isPut {
  439. container.EndUploadBlob(ctx)
  440. } else {
  441. container.CancelUploadBlob(ctx)
  442. }
  443. return
  444. }
  445. m = blobsPattern.FindStringSubmatch(path)
  446. if len(m) == 3 && (isHead || isGet || isDelete) {
  447. ctx.SetParams("image", m[1])
  448. container.VerifyImageName(ctx)
  449. if ctx.Written() {
  450. return
  451. }
  452. ctx.SetParams("digest", m[2])
  453. if isHead {
  454. container.HeadBlob(ctx)
  455. } else if isGet {
  456. container.GetBlob(ctx)
  457. } else {
  458. reqPackageAccess(perm.AccessModeWrite)(ctx)
  459. if ctx.Written() {
  460. return
  461. }
  462. container.DeleteBlob(ctx)
  463. }
  464. return
  465. }
  466. m = manifestsPattern.FindStringSubmatch(path)
  467. if len(m) == 3 && (isHead || isGet || isPut || isDelete) {
  468. ctx.SetParams("image", m[1])
  469. container.VerifyImageName(ctx)
  470. if ctx.Written() {
  471. return
  472. }
  473. ctx.SetParams("reference", m[2])
  474. if isHead {
  475. container.HeadManifest(ctx)
  476. } else if isGet {
  477. container.GetManifest(ctx)
  478. } else {
  479. reqPackageAccess(perm.AccessModeWrite)(ctx)
  480. if ctx.Written() {
  481. return
  482. }
  483. if isPut {
  484. container.UploadManifest(ctx)
  485. } else {
  486. container.DeleteManifest(ctx)
  487. }
  488. }
  489. return
  490. }
  491. ctx.Status(http.StatusNotFound)
  492. })
  493. }, container.ReqContainerAccess, context_service.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead))
  494. return r
  495. }