discourse/lib/email/processor.rb
David Battersby 1de8361d2e
FIX: Prevent Email Processor errors when mail is blank or nil (#21292)
Currently processing emails that are blank or have a nil value for the mail will cause several errors.

This update allows emails with blank body or missing sender to log the blank email error to the mail logs rather than throwing an error.
2023-05-18 10:39:37 +08:00

188 lines
6.3 KiB
Ruby

# frozen_string_literal: true
module Email
class Processor
attr_reader :receiver
def initialize(mail, opts = {})
@mail = mail
@opts = opts
end
def self.process!(mail, opts = {})
Email::Processor.new(mail, opts).process!
end
def process!
begin
@receiver = Email::Receiver.new(@mail, @opts)
@receiver.process!
rescue RateLimiter::LimitExceeded
if @opts[:retry_on_rate_limit]
Jobs.enqueue(:process_email, mail: @mail, source: @opts[:source])
else
raise
end
rescue => e
return handle_bounce(e) if @receiver&.is_bounce?
log_email_process_failure(@mail, e)
incoming_email = @receiver.try(:incoming_email)
rejection_message = handle_failure(@mail, e)
if rejection_message.present?
set_incoming_email_rejection_message(incoming_email, rejection_message.body.to_s)
end
end
end
private
def handle_bounce(e)
# never reply to bounced emails
log_email_process_failure(@mail, e)
set_incoming_email_rejection_message(
@receiver.incoming_email,
I18n.t("emails.incoming.errors.bounced_email_error"),
)
end
def handle_failure(mail_string, e)
message_template =
case e
when Email::Receiver::NoSenderDetectedError
return nil
when Email::Receiver::FromReplyByAddressError
return nil
when Email::Receiver::EmptyEmailError
:email_reject_empty
when Email::Receiver::NoBodyDetectedError
:email_reject_empty
when Email::Receiver::UserNotFoundError
:email_reject_user_not_found
when Email::Receiver::ScreenedEmailError
:email_reject_screened_email
when Email::Receiver::EmailNotAllowed
:email_reject_not_allowed_email
when Email::Receiver::AutoGeneratedEmailError
:email_reject_auto_generated
when Email::Receiver::InactiveUserError
:email_reject_inactive_user
when Email::Receiver::SilencedUserError
:email_reject_silenced_user
when Email::Receiver::BadDestinationAddress
:email_reject_bad_destination_address
when Email::Receiver::StrangersNotAllowedError
:email_reject_strangers_not_allowed
when Email::Receiver::InsufficientTrustLevelError
:email_reject_insufficient_trust_level
when Email::Receiver::ReplyUserNotMatchingError
:email_reject_reply_user_not_matching
when Email::Receiver::TopicNotFoundError
:email_reject_topic_not_found
when Email::Receiver::TopicClosedError
:email_reject_topic_closed
when Email::Receiver::InvalidPost
:email_reject_invalid_post
when Email::Receiver::TooShortPost
:email_reject_post_too_short
when Email::Receiver::UnsubscribeNotAllowed
:email_reject_invalid_post
when ActiveRecord::Rollback
:email_reject_invalid_post
when Email::Receiver::InvalidPostAction
:email_reject_invalid_post_action
when Discourse::InvalidAccess
:email_reject_invalid_access
when Email::Receiver::OldDestinationError
:email_reject_old_destination
when Email::Receiver::ReplyNotAllowedError
:email_reject_reply_not_allowed
when Email::Receiver::ReplyToDigestError
:email_reject_reply_to_digest
when Email::Receiver::TooManyRecipientsError
:email_reject_too_many_recipients
else
:email_reject_unrecognized_error
end
template_args = {}
client_message = nil
# there might be more information available in the exception
if message_template == :email_reject_invalid_post && e.message.size > 6
message_template = :email_reject_invalid_post_specified
template_args[:post_error] = e.message
end
if message_template == :email_reject_post_too_short
template_args[:count] = SiteSetting.min_post_length
end
if message_template == :email_reject_unrecognized_error
msg = "Unrecognized error type (#{e.class}: #{e.message}) when processing incoming email"
msg += "\n\nBacktrace:\n#{e.backtrace.map { |l| " #{l}" }.join("\n")}"
msg += "\n\nMail:\n#{mail_string}"
Rails.logger.error(msg)
end
if message_template == :email_reject_old_destination
template_args[:short_url] = e.message
template_args[:number_of_days] = SiteSetting.disallow_reply_by_email_after_days
end
if message_template == :email_reject_too_many_recipients
template_args[:recipients_count] = e.recipients_count
template_args[:max_recipients_count] = SiteSetting.maximum_recipients_per_new_group_email
end
if message_template
# inform the user about the rejection
message = Mail::Message.new(mail_string)
template_args[:former_title] = message.subject
template_args[:destination] = message.to
template_args[:site_name] = SiteSetting.title
client_message =
RejectionMailer.send_rejection(message_template, message.from, template_args)
# only send one rejection email per day to the same email address
if can_send_rejection_email?(message.from, message_template)
Email::Sender.new(client_message, message_template).send
end
end
client_message
end
def can_send_rejection_email?(email, type)
return false if @receiver&.sent_to_mailinglist_mirror?
return true if type == :email_reject_unrecognized_error
key = "rejection_email:#{email}:#{type}:#{Date.today}"
if Discourse.redis.setnx(key, "1")
Discourse.redis.expire(key, 25.hours)
true
else
false
end
end
def set_incoming_email_rejection_message(incoming_email, message)
if incoming_email
incoming_email.update!(
rejection_message: message,
raw: Email::Cleaner.new(incoming_email.raw, rejected: true).execute,
)
end
end
def log_email_process_failure(mail_string, exception)
if SiteSetting.log_mail_processing_failures
Rails.logger.warn("Email can not be processed: #{exception}\n\n#{mail_string}")
end
end
end
end