forgejo/routers/repo/attachment.go
Farid AYOUJIL f72ce26326
Add Content-Length header to HEAD requests (#14542)
* Add Content-Length header to HEAD requests

This change adds the header Content-Length to HEAD HTTP requests.

The previous behaviour was blocking some Windows executables (i.e
bitsadmin.exe) from downloading files hosted in Gitea.

This along with PR #14541, makes the web server compliant with HTTP RFC 2616 which states
"The methods GET and HEAD MUST be supported by all general-purpose servers"
and
"The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response."

This should also respond to issues #8030 and #14532.

* This change adds the header Content-Length to HEAD HTTP requests

Pass the Size of the content as a parameter to ServeData() instead of
calculating it using ioutil.ReadAll(reader) --> this call is dangerous
and can result in a denial of service.

* Add Content-Length header to HEAD requests

Quick fix for imported dependency not used.

* Check if size is positiv int ...

Co-authored-by: zeripath <art27@cantab.net>
2021-02-05 21:10:40 +01:00

159 lines
3.8 KiB
Go

// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
import (
"fmt"
"net/http"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/upload"
)
// UploadIssueAttachment response for Issue/PR attachments
func UploadIssueAttachment(ctx *context.Context) {
uploadAttachment(ctx, setting.Attachment.AllowedTypes)
}
// UploadReleaseAttachment response for uploading release attachments
func UploadReleaseAttachment(ctx *context.Context) {
uploadAttachment(ctx, setting.Repository.Release.AllowedTypes)
}
// UploadAttachment response for uploading attachments
func uploadAttachment(ctx *context.Context, allowedTypes string) {
if !setting.Attachment.Enabled {
ctx.Error(404, "attachment is not enabled")
return
}
file, header, err := ctx.Req.FormFile("file")
if err != nil {
ctx.Error(500, fmt.Sprintf("FormFile: %v", err))
return
}
defer file.Close()
buf := make([]byte, 1024)
n, _ := file.Read(buf)
if n > 0 {
buf = buf[:n]
}
err = upload.Verify(buf, header.Filename, allowedTypes)
if err != nil {
ctx.Error(400, err.Error())
return
}
attach, err := models.NewAttachment(&models.Attachment{
UploaderID: ctx.User.ID,
Name: header.Filename,
}, buf, file)
if err != nil {
ctx.Error(500, fmt.Sprintf("NewAttachment: %v", err))
return
}
log.Trace("New attachment uploaded: %s", attach.UUID)
ctx.JSON(200, map[string]string{
"uuid": attach.UUID,
})
}
// DeleteAttachment response for deleting issue's attachment
func DeleteAttachment(ctx *context.Context) {
file := ctx.Query("file")
attach, err := models.GetAttachmentByUUID(file)
if err != nil {
ctx.Error(400, err.Error())
return
}
if !ctx.IsSigned || (ctx.User.ID != attach.UploaderID) {
ctx.Error(403)
return
}
err = models.DeleteAttachment(attach, true)
if err != nil {
ctx.Error(500, fmt.Sprintf("DeleteAttachment: %v", err))
return
}
ctx.JSON(200, map[string]string{
"uuid": attach.UUID,
})
}
// GetAttachment serve attachements
func GetAttachment(ctx *context.Context) {
attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid"))
if err != nil {
if models.IsErrAttachmentNotExist(err) {
ctx.Error(404)
} else {
ctx.ServerError("GetAttachmentByUUID", err)
}
return
}
repository, unitType, err := attach.LinkedRepository()
if err != nil {
ctx.ServerError("LinkedRepository", err)
return
}
if repository == nil { //If not linked
if !(ctx.IsSigned && attach.UploaderID == ctx.User.ID) { //We block if not the uploader
ctx.Error(http.StatusNotFound)
return
}
} else { //If we have the repository we check access
perm, err := models.GetUserRepoPermission(repository, ctx.User)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err.Error())
return
}
if !perm.CanRead(unitType) {
ctx.Error(http.StatusNotFound)
return
}
}
if setting.Attachment.ServeDirect {
//If we have a signed url (S3, object storage), redirect to this directly.
u, err := storage.Attachments.URL(attach.RelativePath(), attach.Name)
if u != nil && err == nil {
if err := attach.IncreaseDownloadCount(); err != nil {
ctx.ServerError("Update", err)
return
}
ctx.Redirect(u.String())
return
}
}
//If we have matched and access to release or issue
fr, err := storage.Attachments.Open(attach.RelativePath())
if err != nil {
ctx.ServerError("Open", err)
return
}
defer fr.Close()
if err := attach.IncreaseDownloadCount(); err != nil {
ctx.ServerError("Update", err)
return
}
if err = ServeData(ctx, attach.Name, attach.Size, fr); err != nil {
ctx.ServerError("ServeData", err)
return
}
}