summaryrefslogtreecommitdiff
path: root/lib/gitlab/email/receiver.rb
blob: 97ef9851d71c7240a0f3197815c2d28dad1f3a9d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# Inspired in great part by Discourse's Email::Receiver
module Gitlab
  module Email
    class Receiver
      class ProcessingError < StandardError; end
      class EmailUnparsableError < ProcessingError; end
      class SentNotificationNotFoundError < ProcessingError; end
      class EmptyEmailError < ProcessingError; end
      class AutoGeneratedEmailError < ProcessingError; end
      class UserNotFoundError < ProcessingError; end
      class UserBlockedError < ProcessingError; end
      class UserNotAuthorizedError < ProcessingError; end
      class NoteableNotFoundError < ProcessingError; end
      class InvalidNoteError < ProcessingError; end

      def initialize(raw)
        @raw = raw
      end

      def execute
        raise EmptyEmailError if @raw.blank?

        raise SentNotificationNotFoundError unless sent_notification

        raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/

        author = sent_notification.recipient

        raise UserNotFoundError unless author

        raise UserBlockedError if author.blocked?

        project = sent_notification.project

        raise UserNotAuthorizedError unless project && author.can?(:create_note, project)

        raise NoteableNotFoundError unless sent_notification.noteable

        reply = ReplyParser.new(message).execute.strip

        raise EmptyEmailError if reply.blank?

        reply = add_attachments(reply)

        note = create_note(reply)

        unless note.persisted?
          msg = "The comment could not be created for the following reasons:"
          note.errors.full_messages.each do |error|
            msg << "\n\n- #{error}"
          end

          raise InvalidNoteError, msg
        end
      end

      private

      def message
        @message ||= Mail::Message.new(@raw)
      rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e
        raise EmailUnparsableError, e
      end

      def reply_key
        key_from_to_header || key_from_additional_headers
      end

      def key_from_to_header
        key = nil
        message.to.each do |address|
          key = Gitlab::IncomingEmail.key_from_address(address)
          break if key
        end

        key
      end

      def key_from_additional_headers
        reply_key = nil

        Array(message.references).each do |message_id|
          reply_key = Gitlab::IncomingEmail.key_from_fallback_reply_message_id(message_id)
          break if reply_key
        end

        reply_key
      end

      def sent_notification
        return nil unless reply_key

        SentNotification.for(reply_key)
      end

      def add_attachments(reply)
        attachments = Email::AttachmentUploader.new(message).execute(sent_notification.project)

        attachments.each do |link|
          reply << "\n\n#{link[:markdown]}"
        end

        reply
      end

      def create_note(reply)
        Notes::CreateService.new(
          sent_notification.project,
          sent_notification.recipient,
          note:           reply,
          noteable_type:  sent_notification.noteable_type,
          noteable_id:    sent_notification.noteable_id,
          commit_id:      sent_notification.commit_id,
          line_code:      sent_notification.line_code
        ).execute
      end
    end
  end
end