Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/util/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ var (
ErrPermissionDenied = errors.New("permission denied") // also implies HTTP 403
ErrNotExist = errors.New("resource does not exist") // also implies HTTP 404
ErrAlreadyExist = errors.New("resource already exists") // also implies HTTP 409
ErrContentTooLarge = errors.New("content exceeds limit") // also implies HTTP 413

// ErrUnprocessableContent implies HTTP 422, the syntax of the request content is correct,
// but the server is unable to process the contained instructions
Expand Down
8 changes: 7 additions & 1 deletion routers/api/v1/repo/issue_attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
package repo

import (
"errors"
"net/http"

issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
attachment_service "code.gitea.io/gitea/services/attachment"
"code.gitea.io/gitea/services/context"
Expand Down Expand Up @@ -154,6 +156,8 @@ func CreateIssueAttachment(ctx *context.APIContext) {
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/error"
// "413":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
// "423":
Expand Down Expand Up @@ -181,7 +185,7 @@ func CreateIssueAttachment(ctx *context.APIContext) {
filename = query
}

attachment, err := attachment_service.UploadAttachment(ctx, file, setting.Attachment.AllowedTypes, header.Size, &repo_model.Attachment{
attachment, err := attachment_service.UploadAttachment(ctx, file, setting.Attachment.AllowedTypes, setting.Attachment.MaxSize<<20, header.Size, &repo_model.Attachment{
Name: filename,
UploaderID: ctx.Doer.ID,
RepoID: ctx.Repo.Repository.ID,
Expand All @@ -190,6 +194,8 @@ func CreateIssueAttachment(ctx *context.APIContext) {
if err != nil {
if upload.IsErrFileTypeForbidden(err) {
ctx.APIError(http.StatusUnprocessableEntity, err)
} else if errors.Is(err, util.ErrContentTooLarge) {
ctx.APIError(http.StatusRequestEntityTooLarge, err)
} else {
ctx.APIErrorInternal(err)
}
Expand Down
7 changes: 6 additions & 1 deletion routers/api/v1/repo/issue_comment_attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
attachment_service "code.gitea.io/gitea/services/attachment"
"code.gitea.io/gitea/services/context"
Expand Down Expand Up @@ -161,6 +162,8 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/error"
// "413":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
// "423":
Expand Down Expand Up @@ -189,7 +192,7 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
filename = query
}

attachment, err := attachment_service.UploadAttachment(ctx, file, setting.Attachment.AllowedTypes, header.Size, &repo_model.Attachment{
attachment, err := attachment_service.UploadAttachment(ctx, file, setting.Attachment.AllowedTypes, setting.Attachment.MaxSize<<20, header.Size, &repo_model.Attachment{
Name: filename,
UploaderID: ctx.Doer.ID,
RepoID: ctx.Repo.Repository.ID,
Expand All @@ -199,6 +202,8 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
if err != nil {
if upload.IsErrFileTypeForbidden(err) {
ctx.APIError(http.StatusUnprocessableEntity, err)
} else if errors.Is(err, util.ErrContentTooLarge) {
ctx.APIError(http.StatusRequestEntityTooLarge, err)
} else {
ctx.APIErrorInternal(err)
}
Expand Down
12 changes: 11 additions & 1 deletion routers/api/v1/repo/release_attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package repo

import (
"errors"
"io"
"net/http"
"strings"
Expand All @@ -12,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
attachment_service "code.gitea.io/gitea/services/attachment"
"code.gitea.io/gitea/services/context"
Expand Down Expand Up @@ -191,6 +193,8 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
// "413":
// "$ref": "#/responses/error"

// Check if attachments are enabled
if !setting.Attachment.Enabled {
Expand Down Expand Up @@ -234,7 +238,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
}

// Create a new attachment and save the file
attach, err := attachment_service.UploadAttachment(ctx, content, setting.Repository.Release.AllowedTypes, size, &repo_model.Attachment{
attach, err := attachment_service.UploadAttachment(ctx, content, setting.Repository.Release.AllowedTypes, setting.Attachment.MaxSize<<20, size, &repo_model.Attachment{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are at least 2 "attachment max size" config options.

If you always use setting.Attachment.MaxSize, at least one is wrong

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? looks they are all same in curent design. hasn't found anything like setting.Repository.Release.MaxSize.

ref:

// AddUploadContext renders template values for dropzone
func AddUploadContext(ctx *context.Context, uploadType string) {
switch uploadType {
case "release":
ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/releases/attachments"
ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/releases/attachments/remove"
ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/releases/attachments"
ctx.Data["UploadAccepts"] = strings.ReplaceAll(setting.Repository.Release.AllowedTypes, "|", ",")
ctx.Data["UploadMaxFiles"] = setting.Attachment.MaxFiles
ctx.Data["UploadMaxSize"] = setting.Attachment.MaxSize
case "comment":
ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/issues/attachments"
ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/issues/attachments/remove"
if len(ctx.PathParam("index")) > 0 {
ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/" + url.PathEscape(ctx.PathParam("index")) + "/attachments"
} else {
ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/attachments"
}
ctx.Data["UploadAccepts"] = strings.ReplaceAll(setting.Attachment.AllowedTypes, "|", ",")
ctx.Data["UploadMaxFiles"] = setting.Attachment.MaxFiles
ctx.Data["UploadMaxSize"] = setting.Attachment.MaxSize
default:
setting.PanicInDevOrTesting("Invalid upload type: %s", uploadType)
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh you are right, release upload shares the same attachment max size with comment attachment, but doesn't share some other settings .....

It seems to be another (unrelated) legacy problem.

Copy link
Contributor

@wxiaoguang wxiaoguang Oct 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: the problem I mentioned There are at least 2 "attachment max size" config options. is still there. See my latest comment for UploadFileToServer, there is no limit check either.

Name: filename,
UploaderID: ctx.Doer.ID,
RepoID: ctx.Repo.Repository.ID,
Expand All @@ -245,6 +249,12 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
ctx.APIError(http.StatusBadRequest, err)
return
}

if errors.Is(err, util.ErrContentTooLarge) {
ctx.APIError(http.StatusRequestEntityTooLarge, err)
return
}

ctx.APIErrorInternal(err)
return
}
Expand Down
2 changes: 1 addition & 1 deletion routers/web/repo/attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func uploadAttachment(ctx *context.Context, repoID int64, allowedTypes string) {
}
defer file.Close()

attach, err := attachment.UploadAttachment(ctx, file, allowedTypes, header.Size, &repo_model.Attachment{
attach, err := attachment.UploadAttachment(ctx, file, allowedTypes, setting.Attachment.MaxSize<<20, header.Size, &repo_model.Attachment{
Name: header.Filename,
UploaderID: ctx.Doer.ID,
RepoID: repoID,
Expand Down
19 changes: 18 additions & 1 deletion services/attachment/attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,21 @@ func NewAttachment(ctx context.Context, attach *repo_model.Attachment, file io.R
return attach, err
}

type ErrAttachmentSizeExceed struct {
MaxSize int64
Size int64
}

func (e ErrAttachmentSizeExceed) Error() string {
return fmt.Sprintf("attachment size %d exceeds limit %d", e.Size, e.MaxSize)
}

func (e ErrAttachmentSizeExceed) Unwrap() error {
return util.ErrContentTooLarge
}

// UploadAttachment upload new attachment into storage and update database
func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string, fileSize int64, attach *repo_model.Attachment) (*repo_model.Attachment, error) {
func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string, maxFileSize, fileSize int64, attach *repo_model.Attachment) (*repo_model.Attachment, error) {
buf := make([]byte, 1024)
n, _ := util.ReadAtMost(file, buf)
buf = buf[:n]
Expand All @@ -48,6 +61,10 @@ func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string,
return nil, err
}

if maxFileSize >= 0 && fileSize > maxFileSize {
return nil, ErrAttachmentSizeExceed{MaxSize: maxFileSize, Size: fileSize}
}

return NewAttachment(ctx, attach, io.MultiReader(bytes.NewReader(buf), file), fileSize)
}

Expand Down
8 changes: 7 additions & 1 deletion services/mailer/incoming/incoming_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package incoming
import (
"bytes"
"context"
"errors"
"fmt"

issues_model "code.gitea.io/gitea/models/issues"
Expand Down Expand Up @@ -85,7 +86,7 @@ func (h *ReplyHandler) Handle(ctx context.Context, content *MailContent, doer *u
attachmentIDs := make([]string, 0, len(content.Attachments))
if setting.Attachment.Enabled {
for _, attachment := range content.Attachments {
a, err := attachment_service.UploadAttachment(ctx, bytes.NewReader(attachment.Content), setting.Attachment.AllowedTypes, int64(len(attachment.Content)), &repo_model.Attachment{
a, err := attachment_service.UploadAttachment(ctx, bytes.NewReader(attachment.Content), setting.Attachment.AllowedTypes, setting.Attachment.MaxSize<<20, int64(len(attachment.Content)), &repo_model.Attachment{
Name: attachment.Name,
UploaderID: doer.ID,
RepoID: issue.Repo.ID,
Expand All @@ -95,6 +96,11 @@ func (h *ReplyHandler) Handle(ctx context.Context, content *MailContent, doer *u
log.Info("Skipping disallowed attachment type: %s", attachment.Name)
continue
}
if errors.Is(err, util.ErrContentTooLarge) {
log.Info("Skipping attachment exceeding size limit: %s", attachment.Name)
continue
}

return err
}
attachmentIDs = append(attachmentIDs, a.UUID)
Expand Down
9 changes: 9 additions & 0 deletions templates/swagger/v1_json.tmpl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.