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  }