gitea源码

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