code.gitea.io/gitea@v1.22.3/services/mailer/incoming/incoming_handler.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package incoming 5 6 import ( 7 "bytes" 8 "context" 9 "fmt" 10 11 issues_model "code.gitea.io/gitea/models/issues" 12 access_model "code.gitea.io/gitea/models/perm/access" 13 repo_model "code.gitea.io/gitea/models/repo" 14 user_model "code.gitea.io/gitea/models/user" 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/setting" 17 "code.gitea.io/gitea/modules/util" 18 attachment_service "code.gitea.io/gitea/services/attachment" 19 "code.gitea.io/gitea/services/context/upload" 20 issue_service "code.gitea.io/gitea/services/issue" 21 incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload" 22 "code.gitea.io/gitea/services/mailer/token" 23 pull_service "code.gitea.io/gitea/services/pull" 24 ) 25 26 type MailHandler interface { 27 Handle(ctx context.Context, content *MailContent, doer *user_model.User, payload []byte) error 28 } 29 30 var handlers = map[token.HandlerType]MailHandler{ 31 token.ReplyHandlerType: &ReplyHandler{}, 32 token.UnsubscribeHandlerType: &UnsubscribeHandler{}, 33 } 34 35 // ReplyHandler handles incoming emails to create a reply from them 36 type ReplyHandler struct{} 37 38 func (h *ReplyHandler) Handle(ctx context.Context, content *MailContent, doer *user_model.User, payload []byte) error { 39 if doer == nil { 40 return util.NewInvalidArgumentErrorf("doer can't be nil") 41 } 42 43 ref, err := incoming_payload.GetReferenceFromPayload(ctx, payload) 44 if err != nil { 45 return err 46 } 47 48 var issue *issues_model.Issue 49 50 switch r := ref.(type) { 51 case *issues_model.Issue: 52 issue = r 53 case *issues_model.Comment: 54 comment := r 55 56 if err := comment.LoadIssue(ctx); err != nil { 57 return err 58 } 59 60 issue = comment.Issue 61 default: 62 return util.NewInvalidArgumentErrorf("unsupported reply reference: %v", ref) 63 } 64 65 if err := issue.LoadRepo(ctx); err != nil { 66 return err 67 } 68 69 perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer) 70 if err != nil { 71 return err 72 } 73 74 // Locked issues require write permissions 75 if issue.IsLocked && !perm.CanWriteIssuesOrPulls(issue.IsPull) && !doer.IsAdmin { 76 log.Debug("can't write issue or pull") 77 return nil 78 } 79 80 if !perm.CanReadIssuesOrPulls(issue.IsPull) { 81 log.Debug("can't read issue or pull") 82 return nil 83 } 84 85 attachmentIDs := make([]string, 0, len(content.Attachments)) 86 if setting.Attachment.Enabled { 87 for _, attachment := range content.Attachments { 88 a, err := attachment_service.UploadAttachment(ctx, bytes.NewReader(attachment.Content), setting.Attachment.AllowedTypes, int64(len(attachment.Content)), &repo_model.Attachment{ 89 Name: attachment.Name, 90 UploaderID: doer.ID, 91 RepoID: issue.Repo.ID, 92 }) 93 if err != nil { 94 if upload.IsErrFileTypeForbidden(err) { 95 log.Info("Skipping disallowed attachment type: %s", attachment.Name) 96 continue 97 } 98 return err 99 } 100 attachmentIDs = append(attachmentIDs, a.UUID) 101 } 102 } 103 104 if content.Content == "" && len(attachmentIDs) == 0 { 105 return nil 106 } 107 108 switch r := ref.(type) { 109 case *issues_model.Issue: 110 _, err := issue_service.CreateIssueComment(ctx, doer, issue.Repo, issue, content.Content, attachmentIDs) 111 if err != nil { 112 return fmt.Errorf("CreateIssueComment failed: %w", err) 113 } 114 case *issues_model.Comment: 115 comment := r 116 117 switch comment.Type { 118 case issues_model.CommentTypeCode: 119 _, err := pull_service.CreateCodeComment( 120 ctx, 121 doer, 122 nil, 123 issue, 124 comment.Line, 125 content.Content, 126 comment.TreePath, 127 false, // not pending review but a single review 128 comment.ReviewID, 129 "", 130 attachmentIDs, 131 ) 132 if err != nil { 133 return fmt.Errorf("CreateCodeComment failed: %w", err) 134 } 135 default: 136 _, err := issue_service.CreateIssueComment(ctx, doer, issue.Repo, issue, content.Content, attachmentIDs) 137 if err != nil { 138 return fmt.Errorf("CreateIssueComment failed: %w", err) 139 } 140 } 141 } 142 return nil 143 } 144 145 // UnsubscribeHandler handles unwatching issues/pulls 146 type UnsubscribeHandler struct{} 147 148 func (h *UnsubscribeHandler) Handle(ctx context.Context, _ *MailContent, doer *user_model.User, payload []byte) error { 149 if doer == nil { 150 return util.NewInvalidArgumentErrorf("doer can't be nil") 151 } 152 153 ref, err := incoming_payload.GetReferenceFromPayload(ctx, payload) 154 if err != nil { 155 return err 156 } 157 158 switch r := ref.(type) { 159 case *issues_model.Issue: 160 issue := r 161 162 if err := issue.LoadRepo(ctx); err != nil { 163 return err 164 } 165 166 perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer) 167 if err != nil { 168 return err 169 } 170 171 if !perm.CanReadIssuesOrPulls(issue.IsPull) { 172 log.Debug("can't read issue or pull") 173 return nil 174 } 175 176 return issues_model.CreateOrUpdateIssueWatch(ctx, doer.ID, issue.ID, false) 177 } 178 179 return fmt.Errorf("unsupported unsubscribe reference: %v", ref) 180 }