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