code.gitea.io/gitea@v1.21.7/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/upload" 18 "code.gitea.io/gitea/modules/util" 19 attachment_service "code.gitea.io/gitea/services/attachment" 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 switch r := ref.(type) { 86 case *issues_model.Issue: 87 attachmentIDs := make([]string, 0, len(content.Attachments)) 88 if setting.Attachment.Enabled { 89 for _, attachment := range content.Attachments { 90 a, err := attachment_service.UploadAttachment(bytes.NewReader(attachment.Content), setting.Attachment.AllowedTypes, int64(len(attachment.Content)), &repo_model.Attachment{ 91 Name: attachment.Name, 92 UploaderID: doer.ID, 93 RepoID: issue.Repo.ID, 94 }) 95 if err != nil { 96 if upload.IsErrFileTypeForbidden(err) { 97 log.Info("Skipping disallowed attachment type: %s", attachment.Name) 98 continue 99 } 100 return err 101 } 102 attachmentIDs = append(attachmentIDs, a.UUID) 103 } 104 } 105 106 if content.Content == "" && len(attachmentIDs) == 0 { 107 return nil 108 } 109 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 if content.Content == "" { 118 return nil 119 } 120 121 if comment.Type == issues_model.CommentTypeCode { 122 _, err := pull_service.CreateCodeComment( 123 ctx, 124 doer, 125 nil, 126 issue, 127 comment.Line, 128 content.Content, 129 comment.TreePath, 130 false, // not pending review but a single review 131 comment.ReviewID, 132 "", 133 ) 134 if err != nil { 135 return fmt.Errorf("CreateCodeComment failed: %w", err) 136 } 137 } 138 } 139 return nil 140 } 141 142 // UnsubscribeHandler handles unwatching issues/pulls 143 type UnsubscribeHandler struct{} 144 145 func (h *UnsubscribeHandler) Handle(ctx context.Context, _ *MailContent, doer *user_model.User, payload []byte) error { 146 if doer == nil { 147 return util.NewInvalidArgumentErrorf("doer can't be nil") 148 } 149 150 ref, err := incoming_payload.GetReferenceFromPayload(ctx, payload) 151 if err != nil { 152 return err 153 } 154 155 switch r := ref.(type) { 156 case *issues_model.Issue: 157 issue := r 158 159 if err := issue.LoadRepo(ctx); err != nil { 160 return err 161 } 162 163 perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer) 164 if err != nil { 165 return err 166 } 167 168 if !perm.CanReadIssuesOrPulls(issue.IsPull) { 169 log.Debug("can't read issue or pull") 170 return nil 171 } 172 173 return issues_model.CreateOrUpdateIssueWatch(ctx, doer.ID, issue.ID, false) 174 } 175 176 return fmt.Errorf("unsupported unsubscribe reference: %v", ref) 177 }