9f218fc184
A few things to note: - The IncomingEmail feature is now enabled even without a correctly-formatted sub-address - Message-ID for new thread mail are kept the same so that subsequent notifications to this thread are grouped in the thread by the email service that receives the notification (i.e. In-Reply-To of the answer == Message-ID of the first thread message) - To maximize our chance to be able to retrieve the reply key, we look for it in the In-Reply-To header and the References header - The pattern for the fallback reply message id is "reply-[key]@[gitlab_host]" - Improve docs thanks to Axil
119 lines
3.3 KiB
Ruby
119 lines
3.3 KiB
Ruby
# 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
|