mirror of
https://codeberg.org/forgejo/forgejo
synced 2024-11-30 05:46:09 +01:00
0d9b44c0e3
Backport #23406 by @baez90 When creating attachments (issue, release, repo) the file size (being part of the multipart file header) is passed through the chain of creating an attachment to ensure the MinIO client can stream the file directly instead of having to read it to memory completely at first. Fixes #23393 Co-authored-by: Peter <peter.kurfer@googlemail.com> Co-authored-by: KN4CK3R <admin@oldschoolhack.me> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
178 lines
4.4 KiB
Go
178 lines
4.4 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package incoming
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
|
|
issues_model "code.gitea.io/gitea/models/issues"
|
|
access_model "code.gitea.io/gitea/models/perm/access"
|
|
repo_model "code.gitea.io/gitea/models/repo"
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/modules/upload"
|
|
"code.gitea.io/gitea/modules/util"
|
|
attachment_service "code.gitea.io/gitea/services/attachment"
|
|
issue_service "code.gitea.io/gitea/services/issue"
|
|
incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload"
|
|
"code.gitea.io/gitea/services/mailer/token"
|
|
pull_service "code.gitea.io/gitea/services/pull"
|
|
)
|
|
|
|
type MailHandler interface {
|
|
Handle(ctx context.Context, content *MailContent, doer *user_model.User, payload []byte) error
|
|
}
|
|
|
|
var handlers = map[token.HandlerType]MailHandler{
|
|
token.ReplyHandlerType: &ReplyHandler{},
|
|
token.UnsubscribeHandlerType: &UnsubscribeHandler{},
|
|
}
|
|
|
|
// ReplyHandler handles incoming emails to create a reply from them
|
|
type ReplyHandler struct{}
|
|
|
|
func (h *ReplyHandler) Handle(ctx context.Context, content *MailContent, doer *user_model.User, payload []byte) error {
|
|
if doer == nil {
|
|
return util.NewInvalidArgumentErrorf("doer can't be nil")
|
|
}
|
|
|
|
ref, err := incoming_payload.GetReferenceFromPayload(ctx, payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var issue *issues_model.Issue
|
|
|
|
switch r := ref.(type) {
|
|
case *issues_model.Issue:
|
|
issue = r
|
|
case *issues_model.Comment:
|
|
comment := r
|
|
|
|
if err := comment.LoadIssue(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
issue = comment.Issue
|
|
default:
|
|
return util.NewInvalidArgumentErrorf("unsupported reply reference: %v", ref)
|
|
}
|
|
|
|
if err := issue.LoadRepo(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Locked issues require write permissions
|
|
if issue.IsLocked && !perm.CanWriteIssuesOrPulls(issue.IsPull) && !doer.IsAdmin {
|
|
log.Debug("can't write issue or pull")
|
|
return nil
|
|
}
|
|
|
|
if !perm.CanReadIssuesOrPulls(issue.IsPull) {
|
|
log.Debug("can't read issue or pull")
|
|
return nil
|
|
}
|
|
|
|
switch r := ref.(type) {
|
|
case *issues_model.Issue:
|
|
attachmentIDs := make([]string, 0, len(content.Attachments))
|
|
if setting.Attachment.Enabled {
|
|
for _, attachment := range content.Attachments {
|
|
a, err := attachment_service.UploadAttachment(bytes.NewReader(attachment.Content), setting.Attachment.AllowedTypes, int64(len(attachment.Content)), &repo_model.Attachment{
|
|
Name: attachment.Name,
|
|
UploaderID: doer.ID,
|
|
RepoID: issue.Repo.ID,
|
|
})
|
|
if err != nil {
|
|
if upload.IsErrFileTypeForbidden(err) {
|
|
log.Info("Skipping disallowed attachment type: %s", attachment.Name)
|
|
continue
|
|
}
|
|
return err
|
|
}
|
|
attachmentIDs = append(attachmentIDs, a.UUID)
|
|
}
|
|
}
|
|
|
|
if content.Content == "" && len(attachmentIDs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
_, err = issue_service.CreateIssueComment(ctx, doer, issue.Repo, issue, content.Content, attachmentIDs)
|
|
if err != nil {
|
|
return fmt.Errorf("CreateIssueComment failed: %w", err)
|
|
}
|
|
case *issues_model.Comment:
|
|
comment := r
|
|
|
|
if content.Content == "" {
|
|
return nil
|
|
}
|
|
|
|
if comment.Type == issues_model.CommentTypeCode {
|
|
_, err := pull_service.CreateCodeComment(
|
|
ctx,
|
|
doer,
|
|
nil,
|
|
issue,
|
|
comment.Line,
|
|
content.Content,
|
|
comment.TreePath,
|
|
false,
|
|
comment.ReviewID,
|
|
"",
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("CreateCodeComment failed: %w", err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UnsubscribeHandler handles unwatching issues/pulls
|
|
type UnsubscribeHandler struct{}
|
|
|
|
func (h *UnsubscribeHandler) Handle(ctx context.Context, _ *MailContent, doer *user_model.User, payload []byte) error {
|
|
if doer == nil {
|
|
return util.NewInvalidArgumentErrorf("doer can't be nil")
|
|
}
|
|
|
|
ref, err := incoming_payload.GetReferenceFromPayload(ctx, payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch r := ref.(type) {
|
|
case *issues_model.Issue:
|
|
issue := r
|
|
|
|
if err := issue.LoadRepo(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !perm.CanReadIssuesOrPulls(issue.IsPull) {
|
|
log.Debug("can't read issue or pull")
|
|
return nil
|
|
}
|
|
|
|
return issues_model.CreateOrUpdateIssueWatch(doer.ID, issue.ID, false)
|
|
}
|
|
|
|
return fmt.Errorf("unsupported unsubscribe reference: %v", ref)
|
|
}
|