gitea源码

release_attachment.go 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repo
  4. import (
  5. "io"
  6. "net/http"
  7. "strings"
  8. repo_model "code.gitea.io/gitea/models/repo"
  9. "code.gitea.io/gitea/modules/log"
  10. "code.gitea.io/gitea/modules/setting"
  11. api "code.gitea.io/gitea/modules/structs"
  12. "code.gitea.io/gitea/modules/web"
  13. attachment_service "code.gitea.io/gitea/services/attachment"
  14. "code.gitea.io/gitea/services/context"
  15. "code.gitea.io/gitea/services/context/upload"
  16. "code.gitea.io/gitea/services/convert"
  17. )
  18. func checkReleaseMatchRepo(ctx *context.APIContext, releaseID int64) bool {
  19. release, err := repo_model.GetReleaseByID(ctx, releaseID)
  20. if err != nil {
  21. if repo_model.IsErrReleaseNotExist(err) {
  22. ctx.APIErrorNotFound()
  23. return false
  24. }
  25. ctx.APIErrorInternal(err)
  26. return false
  27. }
  28. if release.RepoID != ctx.Repo.Repository.ID {
  29. ctx.APIErrorNotFound()
  30. return false
  31. }
  32. return true
  33. }
  34. // GetReleaseAttachment gets a single attachment of the release
  35. func GetReleaseAttachment(ctx *context.APIContext) {
  36. // swagger:operation GET /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id} repository repoGetReleaseAttachment
  37. // ---
  38. // summary: Get a release attachment
  39. // produces:
  40. // - application/json
  41. // parameters:
  42. // - name: owner
  43. // in: path
  44. // description: owner of the repo
  45. // type: string
  46. // required: true
  47. // - name: repo
  48. // in: path
  49. // description: name of the repo
  50. // type: string
  51. // required: true
  52. // - name: id
  53. // in: path
  54. // description: id of the release
  55. // type: integer
  56. // format: int64
  57. // required: true
  58. // - name: attachment_id
  59. // in: path
  60. // description: id of the attachment to get
  61. // type: integer
  62. // format: int64
  63. // required: true
  64. // responses:
  65. // "200":
  66. // "$ref": "#/responses/Attachment"
  67. // "404":
  68. // "$ref": "#/responses/notFound"
  69. releaseID := ctx.PathParamInt64("id")
  70. if !checkReleaseMatchRepo(ctx, releaseID) {
  71. return
  72. }
  73. attachID := ctx.PathParamInt64("attachment_id")
  74. attach, err := repo_model.GetAttachmentByID(ctx, attachID)
  75. if err != nil {
  76. if repo_model.IsErrAttachmentNotExist(err) {
  77. ctx.APIErrorNotFound()
  78. return
  79. }
  80. ctx.APIErrorInternal(err)
  81. return
  82. }
  83. if attach.ReleaseID != releaseID {
  84. log.Info("User requested attachment is not in release, release_id %v, attachment_id: %v", releaseID, attachID)
  85. ctx.APIErrorNotFound()
  86. return
  87. }
  88. // FIXME Should prove the existence of the given repo, but results in unnecessary database requests
  89. ctx.JSON(http.StatusOK, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
  90. }
  91. // ListReleaseAttachments lists all attachments of the release
  92. func ListReleaseAttachments(ctx *context.APIContext) {
  93. // swagger:operation GET /repos/{owner}/{repo}/releases/{id}/assets repository repoListReleaseAttachments
  94. // ---
  95. // summary: List release's attachments
  96. // produces:
  97. // - application/json
  98. // parameters:
  99. // - name: owner
  100. // in: path
  101. // description: owner of the repo
  102. // type: string
  103. // required: true
  104. // - name: repo
  105. // in: path
  106. // description: name of the repo
  107. // type: string
  108. // required: true
  109. // - name: id
  110. // in: path
  111. // description: id of the release
  112. // type: integer
  113. // format: int64
  114. // required: true
  115. // responses:
  116. // "200":
  117. // "$ref": "#/responses/AttachmentList"
  118. // "404":
  119. // "$ref": "#/responses/notFound"
  120. releaseID := ctx.PathParamInt64("id")
  121. release, err := repo_model.GetReleaseByID(ctx, releaseID)
  122. if err != nil {
  123. if repo_model.IsErrReleaseNotExist(err) {
  124. ctx.APIErrorNotFound()
  125. return
  126. }
  127. ctx.APIErrorInternal(err)
  128. return
  129. }
  130. if release.RepoID != ctx.Repo.Repository.ID {
  131. ctx.APIErrorNotFound()
  132. return
  133. }
  134. if err := release.LoadAttributes(ctx); err != nil {
  135. ctx.APIErrorInternal(err)
  136. return
  137. }
  138. ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release).Attachments)
  139. }
  140. // CreateReleaseAttachment creates an attachment and saves the given file
  141. func CreateReleaseAttachment(ctx *context.APIContext) {
  142. // swagger:operation POST /repos/{owner}/{repo}/releases/{id}/assets repository repoCreateReleaseAttachment
  143. // ---
  144. // summary: Create a release attachment
  145. // produces:
  146. // - application/json
  147. // consumes:
  148. // - multipart/form-data
  149. // - application/octet-stream
  150. // parameters:
  151. // - name: owner
  152. // in: path
  153. // description: owner of the repo
  154. // type: string
  155. // required: true
  156. // - name: repo
  157. // in: path
  158. // description: name of the repo
  159. // type: string
  160. // required: true
  161. // - name: id
  162. // in: path
  163. // description: id of the release
  164. // type: integer
  165. // format: int64
  166. // required: true
  167. // - name: name
  168. // in: query
  169. // description: name of the attachment
  170. // type: string
  171. // required: false
  172. // - name: attachment
  173. // in: formData
  174. // description: attachment to upload
  175. // type: file
  176. // required: false
  177. // responses:
  178. // "201":
  179. // "$ref": "#/responses/Attachment"
  180. // "400":
  181. // "$ref": "#/responses/error"
  182. // "404":
  183. // "$ref": "#/responses/notFound"
  184. // Check if attachments are enabled
  185. if !setting.Attachment.Enabled {
  186. ctx.APIErrorNotFound("Attachment is not enabled")
  187. return
  188. }
  189. // Check if release exists an load release
  190. releaseID := ctx.PathParamInt64("id")
  191. if !checkReleaseMatchRepo(ctx, releaseID) {
  192. return
  193. }
  194. // Get uploaded file from request
  195. var content io.ReadCloser
  196. var filename string
  197. var size int64 = -1
  198. if strings.HasPrefix(strings.ToLower(ctx.Req.Header.Get("Content-Type")), "multipart/form-data") {
  199. file, header, err := ctx.Req.FormFile("attachment")
  200. if err != nil {
  201. ctx.APIErrorInternal(err)
  202. return
  203. }
  204. defer file.Close()
  205. content = file
  206. size = header.Size
  207. filename = header.Filename
  208. if name := ctx.FormString("name"); name != "" {
  209. filename = name
  210. }
  211. } else {
  212. content = ctx.Req.Body
  213. filename = ctx.FormString("name")
  214. }
  215. if filename == "" {
  216. ctx.APIError(http.StatusBadRequest, "Could not determine name of attachment.")
  217. return
  218. }
  219. // Create a new attachment and save the file
  220. attach, err := attachment_service.UploadAttachment(ctx, content, setting.Repository.Release.AllowedTypes, size, &repo_model.Attachment{
  221. Name: filename,
  222. UploaderID: ctx.Doer.ID,
  223. RepoID: ctx.Repo.Repository.ID,
  224. ReleaseID: releaseID,
  225. })
  226. if err != nil {
  227. if upload.IsErrFileTypeForbidden(err) {
  228. ctx.APIError(http.StatusBadRequest, err)
  229. return
  230. }
  231. ctx.APIErrorInternal(err)
  232. return
  233. }
  234. ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
  235. }
  236. // EditReleaseAttachment updates the given attachment
  237. func EditReleaseAttachment(ctx *context.APIContext) {
  238. // swagger:operation PATCH /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id} repository repoEditReleaseAttachment
  239. // ---
  240. // summary: Edit a release attachment
  241. // produces:
  242. // - application/json
  243. // consumes:
  244. // - application/json
  245. // parameters:
  246. // - name: owner
  247. // in: path
  248. // description: owner of the repo
  249. // type: string
  250. // required: true
  251. // - name: repo
  252. // in: path
  253. // description: name of the repo
  254. // type: string
  255. // required: true
  256. // - name: id
  257. // in: path
  258. // description: id of the release
  259. // type: integer
  260. // format: int64
  261. // required: true
  262. // - name: attachment_id
  263. // in: path
  264. // description: id of the attachment to edit
  265. // type: integer
  266. // format: int64
  267. // required: true
  268. // - name: body
  269. // in: body
  270. // schema:
  271. // "$ref": "#/definitions/EditAttachmentOptions"
  272. // responses:
  273. // "201":
  274. // "$ref": "#/responses/Attachment"
  275. // "422":
  276. // "$ref": "#/responses/validationError"
  277. // "404":
  278. // "$ref": "#/responses/notFound"
  279. form := web.GetForm(ctx).(*api.EditAttachmentOptions)
  280. // Check if release exists an load release
  281. releaseID := ctx.PathParamInt64("id")
  282. if !checkReleaseMatchRepo(ctx, releaseID) {
  283. return
  284. }
  285. attachID := ctx.PathParamInt64("attachment_id")
  286. attach, err := repo_model.GetAttachmentByID(ctx, attachID)
  287. if err != nil {
  288. if repo_model.IsErrAttachmentNotExist(err) {
  289. ctx.APIErrorNotFound()
  290. return
  291. }
  292. ctx.APIErrorInternal(err)
  293. return
  294. }
  295. if attach.ReleaseID != releaseID {
  296. log.Info("User requested attachment is not in release, release_id %v, attachment_id: %v", releaseID, attachID)
  297. ctx.APIErrorNotFound()
  298. return
  299. }
  300. // FIXME Should prove the existence of the given repo, but results in unnecessary database requests
  301. if form.Name != "" {
  302. attach.Name = form.Name
  303. }
  304. if err := attachment_service.UpdateAttachment(ctx, setting.Repository.Release.AllowedTypes, attach); err != nil {
  305. if upload.IsErrFileTypeForbidden(err) {
  306. ctx.APIError(http.StatusUnprocessableEntity, err)
  307. return
  308. }
  309. ctx.APIErrorInternal(err)
  310. return
  311. }
  312. ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
  313. }
  314. // DeleteReleaseAttachment delete a given attachment
  315. func DeleteReleaseAttachment(ctx *context.APIContext) {
  316. // swagger:operation DELETE /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id} repository repoDeleteReleaseAttachment
  317. // ---
  318. // summary: Delete a release attachment
  319. // produces:
  320. // - application/json
  321. // parameters:
  322. // - name: owner
  323. // in: path
  324. // description: owner of the repo
  325. // type: string
  326. // required: true
  327. // - name: repo
  328. // in: path
  329. // description: name of the repo
  330. // type: string
  331. // required: true
  332. // - name: id
  333. // in: path
  334. // description: id of the release
  335. // type: integer
  336. // format: int64
  337. // required: true
  338. // - name: attachment_id
  339. // in: path
  340. // description: id of the attachment to delete
  341. // type: integer
  342. // format: int64
  343. // required: true
  344. // responses:
  345. // "204":
  346. // "$ref": "#/responses/empty"
  347. // "404":
  348. // "$ref": "#/responses/notFound"
  349. // Check if release exists an load release
  350. releaseID := ctx.PathParamInt64("id")
  351. if !checkReleaseMatchRepo(ctx, releaseID) {
  352. return
  353. }
  354. attachID := ctx.PathParamInt64("attachment_id")
  355. attach, err := repo_model.GetAttachmentByID(ctx, attachID)
  356. if err != nil {
  357. if repo_model.IsErrAttachmentNotExist(err) {
  358. ctx.APIErrorNotFound()
  359. return
  360. }
  361. ctx.APIErrorInternal(err)
  362. return
  363. }
  364. if attach.ReleaseID != releaseID {
  365. log.Info("User requested attachment is not in release, release_id %v, attachment_id: %v", releaseID, attachID)
  366. ctx.APIErrorNotFound()
  367. return
  368. }
  369. // FIXME Should prove the existence of the given repo, but results in unnecessary database requests
  370. if err := repo_model.DeleteAttachment(ctx, attach, true); err != nil {
  371. ctx.APIErrorInternal(err)
  372. return
  373. }
  374. ctx.Status(http.StatusNoContent)
  375. }