You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

attachment.go 13 kB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package repo
  5. import (
  6. contexExt "context"
  7. "fmt"
  8. "net/http"
  9. "strconv"
  10. "strings"
  11. "code.gitea.io/gitea/models"
  12. "code.gitea.io/gitea/modules/context"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/minio_ext"
  15. "code.gitea.io/gitea/modules/setting"
  16. "code.gitea.io/gitea/modules/storage"
  17. "code.gitea.io/gitea/modules/upload"
  18. "code.gitea.io/gitea/modules/worker"
  19. gouuid "github.com/satori/go.uuid"
  20. )
  21. const (
  22. //result of decompress
  23. DecompressSuccess = "0"
  24. DecompressFailed = "1"
  25. )
  26. func RenderAttachmentSettings(ctx *context.Context) {
  27. renderAttachmentSettings(ctx)
  28. }
  29. func renderAttachmentSettings(ctx *context.Context) {
  30. ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
  31. ctx.Data["AttachmentStoreType"] = setting.Attachment.StoreType
  32. ctx.Data["AttachmentAllowedTypes"] = setting.Attachment.AllowedTypes
  33. ctx.Data["AttachmentMaxSize"] = setting.Attachment.MaxSize
  34. ctx.Data["AttachmentMaxFiles"] = setting.Attachment.MaxFiles
  35. }
  36. // UploadAttachment response for uploading issue's attachment
  37. func UploadAttachment(ctx *context.Context) {
  38. if !setting.Attachment.Enabled {
  39. ctx.Error(404, "attachment is not enabled")
  40. return
  41. }
  42. file, header, err := ctx.Req.FormFile("file")
  43. if err != nil {
  44. ctx.Error(500, fmt.Sprintf("FormFile: %v", err))
  45. return
  46. }
  47. defer file.Close()
  48. buf := make([]byte, 1024)
  49. n, _ := file.Read(buf)
  50. if n > 0 {
  51. buf = buf[:n]
  52. }
  53. err = upload.VerifyAllowedContentType(buf, strings.Split(setting.Attachment.AllowedTypes, ","))
  54. if err != nil {
  55. ctx.Error(400, err.Error())
  56. return
  57. }
  58. datasetID, _ := strconv.ParseInt(ctx.Req.FormValue("dataset_id"), 10, 64)
  59. attach, err := models.NewAttachment(&models.Attachment{
  60. IsPrivate: true,
  61. UploaderID: ctx.User.ID,
  62. Name: header.Filename,
  63. DatasetID: datasetID,
  64. }, buf, file)
  65. if err != nil {
  66. ctx.Error(500, fmt.Sprintf("NewAttachment: %v", err))
  67. return
  68. }
  69. log.Trace("New attachment uploaded: %s", attach.UUID)
  70. ctx.JSON(200, map[string]string{
  71. "uuid": attach.UUID,
  72. })
  73. }
  74. func UpdatePublicAttachment(ctx *context.Context) {
  75. file := ctx.Query("file")
  76. isPrivate, _ := strconv.ParseBool(ctx.Query("is_private"))
  77. attach, err := models.GetAttachmentByUUID(file)
  78. if err != nil {
  79. ctx.Error(404, err.Error())
  80. return
  81. }
  82. attach.IsPrivate = isPrivate
  83. models.UpdateAttachment(attach)
  84. }
  85. // DeleteAttachment response for deleting issue's attachment
  86. func DeleteAttachment(ctx *context.Context) {
  87. file := ctx.Query("file")
  88. attach, err := models.GetAttachmentByUUID(file)
  89. if err != nil {
  90. ctx.Error(400, err.Error())
  91. return
  92. }
  93. if !ctx.IsSigned || (ctx.User.ID != attach.UploaderID) {
  94. ctx.Error(403)
  95. return
  96. }
  97. err = models.DeleteAttachment(attach, true)
  98. if err != nil {
  99. ctx.Error(500, fmt.Sprintf("DeleteAttachment: %v", err))
  100. return
  101. }
  102. ctx.JSON(200, map[string]string{
  103. "uuid": attach.UUID,
  104. })
  105. }
  106. // GetAttachment serve attachements
  107. func GetAttachment(ctx *context.Context) {
  108. attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid"))
  109. if err != nil {
  110. if models.IsErrAttachmentNotExist(err) {
  111. ctx.Error(404)
  112. } else {
  113. ctx.ServerError("GetAttachmentByUUID", err)
  114. }
  115. return
  116. }
  117. repository, unitType, err := attach.LinkedRepository()
  118. if err != nil {
  119. ctx.ServerError("LinkedRepository", err)
  120. return
  121. }
  122. if repository == nil { //If not linked
  123. if !(ctx.IsSigned && attach.UploaderID == ctx.User.ID) { //We block if not the uploader
  124. ctx.Error(http.StatusNotFound)
  125. return
  126. }
  127. } else { //If we have the repository we check access
  128. perm, err := models.GetUserRepoPermission(repository, ctx.User)
  129. if err != nil {
  130. ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err.Error())
  131. return
  132. }
  133. if !perm.CanRead(unitType) {
  134. ctx.Error(http.StatusNotFound)
  135. return
  136. }
  137. }
  138. dataSet, err := attach.LinkedDataSet()
  139. if err != nil {
  140. ctx.ServerError("LinkedDataSet", err)
  141. return
  142. }
  143. if dataSet != nil {
  144. isPermit, err := models.GetUserDataSetPermission(dataSet, ctx.User)
  145. if err != nil {
  146. ctx.Error(http.StatusInternalServerError, "GetUserDataSetPermission", err.Error())
  147. return
  148. }
  149. if !isPermit {
  150. ctx.Error(http.StatusNotFound)
  151. return
  152. }
  153. }
  154. //If we have matched and access to release or issue
  155. if setting.Attachment.StoreType == storage.MinioStorageType {
  156. url, err := storage.Attachments.PresignedGetURL(attach.RelativePath(), attach.Name)
  157. if err != nil {
  158. ctx.ServerError("PresignedGetURL", err)
  159. return
  160. }
  161. if err = increaseDownloadCount(attach, dataSet); err != nil {
  162. ctx.ServerError("Update", err)
  163. return
  164. }
  165. http.Redirect(ctx.Resp, ctx.Req.Request, url, http.StatusMovedPermanently)
  166. } else {
  167. fr, err := storage.Attachments.Open(attach.RelativePath())
  168. if err != nil {
  169. ctx.ServerError("Open", err)
  170. return
  171. }
  172. defer fr.Close()
  173. if err = increaseDownloadCount(attach, dataSet); err != nil {
  174. ctx.ServerError("Update", err)
  175. return
  176. }
  177. if err = ServeData(ctx, attach.Name, fr); err != nil {
  178. ctx.ServerError("ServeData", err)
  179. return
  180. }
  181. }
  182. }
  183. func increaseDownloadCount(attach *models.Attachment, dataSet *models.Dataset) error {
  184. if err := attach.IncreaseDownloadCount(); err != nil {
  185. return err
  186. }
  187. if dataSet != nil {
  188. if err := models.IncreaseDownloadCount(dataSet.ID); err != nil {
  189. return err
  190. }
  191. }
  192. return nil
  193. }
  194. // Get a presigned url for put object
  195. func GetPresignedPutObjectURL(ctx *context.Context) {
  196. if !setting.Attachment.Enabled {
  197. ctx.Error(404, "attachment is not enabled")
  198. return
  199. }
  200. err := upload.VerifyFileType(ctx.Params("file_type"), strings.Split(setting.Attachment.AllowedTypes, ","))
  201. if err != nil {
  202. ctx.Error(400, err.Error())
  203. return
  204. }
  205. if setting.Attachment.StoreType == storage.MinioStorageType {
  206. uuid := gouuid.NewV4().String()
  207. url, err := storage.Attachments.PresignedPutURL(models.AttachmentRelativePath(uuid))
  208. if err != nil {
  209. ctx.ServerError("PresignedPutURL", err)
  210. return
  211. }
  212. ctx.JSON(200, map[string]string{
  213. "uuid": uuid,
  214. "url": url,
  215. })
  216. } else {
  217. ctx.Error(404, "storage type is not enabled")
  218. return
  219. }
  220. }
  221. // AddAttachment response for add attachment record
  222. func AddAttachment(ctx *context.Context) {
  223. uuid := ctx.Query("uuid")
  224. has, err := storage.Attachments.HasObject(models.AttachmentRelativePath(uuid))
  225. if err != nil {
  226. ctx.ServerError("HasObject", err)
  227. return
  228. }
  229. if !has {
  230. ctx.Error(404, "attachment has not been uploaded")
  231. return
  232. }
  233. attachment, err := models.InsertAttachment(&models.Attachment{
  234. UUID: uuid,
  235. UploaderID: ctx.User.ID,
  236. IsPrivate: true,
  237. Name: ctx.Query("file_name"),
  238. Size: ctx.QueryInt64("size"),
  239. DatasetID: ctx.QueryInt64("dataset_id"),
  240. })
  241. if err != nil {
  242. ctx.Error(500, fmt.Sprintf("InsertAttachment: %v", err))
  243. return
  244. }
  245. if attachment.DatasetID != 0 {
  246. if strings.HasSuffix(attachment.Name, ".zip") {
  247. err = worker.SendDecompressTask(contexExt.Background(), uuid)
  248. if err != nil {
  249. log.Error("SendDecompressTask(%s) failed:%s", uuid, err.Error())
  250. } else {
  251. attachment.DecompressState = models.DecompressStateIng
  252. err = models.UpdateAttachment(attachment)
  253. if err != nil {
  254. log.Error("UpdateAttachment state(%s) failed:%s", uuid, err.Error())
  255. }
  256. }
  257. }
  258. }
  259. ctx.JSON(200, map[string]string{
  260. "result_code": "0",
  261. })
  262. }
  263. func UpdateAttachmentDecompressState(ctx *context.Context) {
  264. uuid := ctx.Query("uuid")
  265. result := ctx.Query("result")
  266. attach, err := models.GetAttachmentByUUID(uuid)
  267. if err != nil {
  268. log.Error("GetAttachmentByUUID(%s) failed:%s", uuid, err.Error())
  269. return
  270. }
  271. if result == DecompressSuccess {
  272. attach.DecompressState = models.DecompressStateDone
  273. } else if result == DecompressFailed {
  274. attach.DecompressState = models.DecompressStateFailed
  275. } else {
  276. log.Error("result is error:", result)
  277. return
  278. }
  279. err = models.UpdateAttachment(attach)
  280. if err != nil {
  281. log.Error("UpdateAttachment(%s) failed:%s", uuid, err.Error())
  282. return
  283. }
  284. ctx.JSON(200, map[string]string{
  285. "result_code": "0",
  286. })
  287. }
  288. func GetSuccessChunks(ctx *context.Context) {
  289. fileMD5 := ctx.Query("md5")
  290. fileChunk, err := models.GetFileChunkByMD5(fileMD5)
  291. if err != nil {
  292. if models.IsErrFileChunkNotExist(err) {
  293. ctx.JSON(200, map[string]string{
  294. "uuid": "",
  295. "uploaded": "0",
  296. "uploadID": "",
  297. "chunks": "",
  298. })
  299. } else {
  300. ctx.ServerError("GetFileChunkByMD5", err)
  301. }
  302. return
  303. }
  304. ctx.JSON(200, map[string]string{
  305. "uuid": fileChunk.UUID,
  306. "uploaded": strconv.Itoa(fileChunk.IsUploaded),
  307. "uploadID":fileChunk.UploadID,
  308. "chunks": fileChunk.HasUploaded,
  309. })
  310. }
  311. func NewMultipart(ctx *context.Context) {
  312. if !setting.Attachment.Enabled {
  313. ctx.Error(404, "attachment is not enabled")
  314. return
  315. }
  316. err := upload.VerifyFileType(ctx.Query("fileType"), strings.Split(setting.Attachment.AllowedTypes, ","))
  317. if err != nil {
  318. ctx.Error(400, err.Error())
  319. return
  320. }
  321. if setting.Attachment.StoreType == storage.MinioStorageType {
  322. totalChunkCounts := ctx.QueryInt("totalChunkCounts")
  323. if totalChunkCounts > minio_ext.MaxPartsCount {
  324. ctx.Error(400, fmt.Sprintf("chunk counts(%d) is too much", totalChunkCounts))
  325. return
  326. }
  327. fileSize := ctx.QueryInt64("size")
  328. if fileSize > minio_ext.MaxMultipartPutObjectSize {
  329. ctx.Error(400, fmt.Sprintf("file size(%d) is too big", fileSize))
  330. return
  331. }
  332. uuid := gouuid.NewV4().String()
  333. uploadID, err := storage.NewMultiPartUpload(uuid)
  334. if err != nil {
  335. ctx.ServerError("NewMultipart", err)
  336. return
  337. }
  338. _, err = models.InsertFileChunk(&models.FileChunk{
  339. UUID: uuid,
  340. UserID: ctx.User.ID,
  341. UploadID: uploadID,
  342. Md5: ctx.Query("md5"),
  343. Size: fileSize,
  344. TotalChunks:totalChunkCounts,
  345. })
  346. if err != nil {
  347. ctx.Error(500, fmt.Sprintf("InsertFileChunk: %v", err))
  348. return
  349. }
  350. ctx.JSON(200, map[string]string{
  351. "uuid": uuid,
  352. "uploadID": uploadID,
  353. })
  354. } else {
  355. ctx.Error(404, "storage type is not enabled")
  356. return
  357. }
  358. }
  359. func GetMultipartUploadUrl(ctx *context.Context) {
  360. uuid := ctx.Query("uuid")
  361. uploadID := ctx.Query("uploadID")
  362. partNumber := ctx.QueryInt("chunkNumber")
  363. size := ctx.QueryInt64("size")
  364. if size > minio_ext.MinPartSize {
  365. ctx.Error(400, fmt.Sprintf("chunk size(%d) is too big", size))
  366. return
  367. }
  368. url,err := storage.GenMultiPartSignedUrl(uuid, uploadID, partNumber, size)
  369. if err != nil {
  370. ctx.Error(500, fmt.Sprintf("GenMultiPartSignedUrl failed: %v", err))
  371. return
  372. }
  373. ctx.JSON(200, map[string]string{
  374. "url": url,
  375. })
  376. }
  377. func CompleteMultipart(ctx *context.Context) {
  378. uuid := ctx.Query("uuid")
  379. uploadID := ctx.Query("uploadID")
  380. completedParts := ctx.Query("completedParts")
  381. fileChunk, err := models.GetFileChunkByUUID(uuid)
  382. if err != nil {
  383. if models.IsErrFileChunkNotExist(err) {
  384. ctx.Error(404)
  385. } else {
  386. ctx.ServerError("GetFileChunkByUUID", err)
  387. }
  388. return
  389. }
  390. _, err = storage.CompleteMultiPartUpload(uuid, uploadID, completedParts)
  391. if err != nil {
  392. ctx.Error(500, fmt.Sprintf("CompleteMultiPartUpload failed: %v", err))
  393. return
  394. }
  395. fileChunk.IsUploaded = models.FileUploaded
  396. err = models.UpdateFileChunk(fileChunk)
  397. if err != nil {
  398. ctx.Error(500, fmt.Sprintf("UpdateFileChunk: %v", err))
  399. return
  400. }
  401. _, err = models.InsertAttachment(&models.Attachment{
  402. UUID: uuid,
  403. UploaderID: ctx.User.ID,
  404. IsPrivate: true,
  405. Name: ctx.Query("file_name"),
  406. Size: ctx.QueryInt64("size"),
  407. DatasetID: ctx.QueryInt64("dataset_id"),
  408. })
  409. if err != nil {
  410. ctx.Error(500, fmt.Sprintf("InsertAttachment: %v", err))
  411. return
  412. }
  413. ctx.JSON(200, map[string]string{
  414. "result_code": "0",
  415. })
  416. }
  417. func UpdateMultipart(ctx *context.Context) {
  418. uuid := ctx.Query("uuid")
  419. partNumber := ctx.QueryInt("partNumber")
  420. fileChunk, err := models.GetFileChunkByUUID(uuid)
  421. if err != nil {
  422. if models.IsErrFileChunkNotExist(err) {
  423. ctx.Error(404)
  424. } else {
  425. ctx.ServerError("GetFileChunkByUUID", err)
  426. }
  427. return
  428. }
  429. fileChunk.HasUploaded += "," + strconv.Itoa(partNumber)
  430. //todo:chunk parts
  431. err = models.UpdateFileChunk(fileChunk)
  432. if err != nil {
  433. ctx.Error(500, fmt.Sprintf("UpdateFileChunk: %v", err))
  434. return
  435. }
  436. ctx.JSON(200, map[string]string{
  437. "result_code": "0",
  438. })
  439. }
  440. func HandleUnDecompressAttachment() {
  441. attachs,err := models.GetUnDecompressAttachments()
  442. if err != nil {
  443. log.Error("GetUnDecompressAttachments failed:", err.Error())
  444. return
  445. }
  446. for _,attach := range attachs {
  447. err = worker.SendDecompressTask(contexExt.Background(), attach.UUID)
  448. if err != nil {
  449. log.Error("SendDecompressTask(%s) failed:%s", attach.UUID, err.Error())
  450. } else {
  451. attach.DecompressState = models.DecompressStateIng
  452. err = models.UpdateAttachment(attach)
  453. if err != nil {
  454. log.Error("UpdateAttachment state(%s) failed:%s", attach.UUID, err.Error())
  455. }
  456. }
  457. }
  458. return
  459. }