code.gitea.io/gitea@v1.21.7/routers/web/repo/attachment.go (about) 1 // Copyright 2017 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package repo 5 6 import ( 7 "fmt" 8 "net/http" 9 10 access_model "code.gitea.io/gitea/models/perm/access" 11 repo_model "code.gitea.io/gitea/models/repo" 12 "code.gitea.io/gitea/modules/context" 13 "code.gitea.io/gitea/modules/httpcache" 14 "code.gitea.io/gitea/modules/log" 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/util" 19 "code.gitea.io/gitea/routers/common" 20 "code.gitea.io/gitea/services/attachment" 21 repo_service "code.gitea.io/gitea/services/repository" 22 ) 23 24 // UploadIssueAttachment response for Issue/PR attachments 25 func UploadIssueAttachment(ctx *context.Context) { 26 uploadAttachment(ctx, ctx.Repo.Repository.ID, setting.Attachment.AllowedTypes) 27 } 28 29 // UploadReleaseAttachment response for uploading release attachments 30 func UploadReleaseAttachment(ctx *context.Context) { 31 uploadAttachment(ctx, ctx.Repo.Repository.ID, setting.Repository.Release.AllowedTypes) 32 } 33 34 // UploadAttachment response for uploading attachments 35 func uploadAttachment(ctx *context.Context, repoID int64, allowedTypes string) { 36 if !setting.Attachment.Enabled { 37 ctx.Error(http.StatusNotFound, "attachment is not enabled") 38 return 39 } 40 41 file, header, err := ctx.Req.FormFile("file") 42 if err != nil { 43 ctx.Error(http.StatusInternalServerError, fmt.Sprintf("FormFile: %v", err)) 44 return 45 } 46 defer file.Close() 47 48 attach, err := attachment.UploadAttachment(file, allowedTypes, header.Size, &repo_model.Attachment{ 49 Name: header.Filename, 50 UploaderID: ctx.Doer.ID, 51 RepoID: repoID, 52 }) 53 if err != nil { 54 if upload.IsErrFileTypeForbidden(err) { 55 ctx.Error(http.StatusBadRequest, err.Error()) 56 return 57 } 58 ctx.Error(http.StatusInternalServerError, fmt.Sprintf("NewAttachment: %v", err)) 59 return 60 } 61 62 log.Trace("New attachment uploaded: %s", attach.UUID) 63 ctx.JSON(http.StatusOK, map[string]string{ 64 "uuid": attach.UUID, 65 }) 66 } 67 68 // DeleteAttachment response for deleting issue's attachment 69 func DeleteAttachment(ctx *context.Context) { 70 file := ctx.FormString("file") 71 attach, err := repo_model.GetAttachmentByUUID(ctx, file) 72 if err != nil { 73 ctx.Error(http.StatusBadRequest, err.Error()) 74 return 75 } 76 if !ctx.IsSigned || (ctx.Doer.ID != attach.UploaderID) { 77 ctx.Error(http.StatusForbidden) 78 return 79 } 80 err = repo_model.DeleteAttachment(ctx, attach, true) 81 if err != nil { 82 ctx.Error(http.StatusInternalServerError, fmt.Sprintf("DeleteAttachment: %v", err)) 83 return 84 } 85 ctx.JSON(http.StatusOK, map[string]string{ 86 "uuid": attach.UUID, 87 }) 88 } 89 90 // GetAttachment serve attachments with the given UUID 91 func ServeAttachment(ctx *context.Context, uuid string) { 92 attach, err := repo_model.GetAttachmentByUUID(ctx, uuid) 93 if err != nil { 94 if repo_model.IsErrAttachmentNotExist(err) { 95 ctx.Error(http.StatusNotFound) 96 } else { 97 ctx.ServerError("GetAttachmentByUUID", err) 98 } 99 return 100 } 101 102 repository, unitType, err := repo_service.LinkedRepository(ctx, attach) 103 if err != nil { 104 ctx.ServerError("LinkedRepository", err) 105 return 106 } 107 108 if repository == nil { // If not linked 109 if !(ctx.IsSigned && attach.UploaderID == ctx.Doer.ID) { // We block if not the uploader 110 ctx.Error(http.StatusNotFound) 111 return 112 } 113 } else { // If we have the repository we check access 114 perm, err := access_model.GetUserRepoPermission(ctx, repository, ctx.Doer) 115 if err != nil { 116 ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err.Error()) 117 return 118 } 119 if !perm.CanRead(unitType) { 120 ctx.Error(http.StatusNotFound) 121 return 122 } 123 } 124 125 if err := attach.IncreaseDownloadCount(ctx); err != nil { 126 ctx.ServerError("IncreaseDownloadCount", err) 127 return 128 } 129 130 if setting.Attachment.Storage.MinioConfig.ServeDirect { 131 // If we have a signed url (S3, object storage), redirect to this directly. 132 u, err := storage.Attachments.URL(attach.RelativePath(), attach.Name) 133 134 if u != nil && err == nil { 135 ctx.Redirect(u.String()) 136 return 137 } 138 } 139 140 if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+attach.UUID+`"`) { 141 return 142 } 143 144 // If we have matched and access to release or issue 145 fr, err := storage.Attachments.Open(attach.RelativePath()) 146 if err != nil { 147 ctx.ServerError("Open", err) 148 return 149 } 150 defer fr.Close() 151 152 common.ServeContentByReadSeeker(ctx.Base, attach.Name, util.ToPointer(attach.CreatedUnix.AsTime()), fr) 153 } 154 155 // GetAttachment serve attachments 156 func GetAttachment(ctx *context.Context) { 157 ServeAttachment(ctx, ctx.Params(":uuid")) 158 }