Martin Brennan c187ede3c6
FIX: Catch UndefinedConversionError for inbound emails ()
Some emails coming in via the mail receiver can still end up
with bad encoding when trying to enqueue the job. This catches
the last encoding issue and forces iso-8559-1 and encodes to
UTF-8 to circumvent the issue.
2021-05-10 14:26:23 +10:00

266 lines
8.3 KiB
Ruby

# frozen_string_literal: true
class Admin::EmailController < Admin::AdminController
def index
data = { delivery_method: delivery_method, settings: delivery_settings }
render_json_dump(data)
end
def test
params.require(:email_address)
begin
message = TestMailer.send_test(params[:email_address])
Email::Sender.new(message, :test_message).send
render json: { sent_test_email_message: I18n.t("admin.email.sent_test") }
rescue => e
render json: { errors: [e.message] }, status: 422
end
end
def sent
email_logs = EmailLog.joins(<<~SQL)
LEFT JOIN post_reply_keys
ON post_reply_keys.post_id = email_logs.post_id
AND post_reply_keys.user_id = email_logs.user_id
SQL
email_logs = filter_logs(email_logs, params)
if (reply_key = params[:reply_key]).present?
email_logs =
if reply_key.length == 32
email_logs.where("post_reply_keys.reply_key = ?", reply_key)
else
email_logs.where(
"replace(post_reply_keys.reply_key::VARCHAR, '-', '') ILIKE ?",
"%#{reply_key}%"
)
end
end
email_logs = email_logs.to_a
tuples = email_logs.map do |email_log|
[email_log.post_id, email_log.user_id]
end
reply_keys = {}
if tuples.present?
PostReplyKey
.where(
"(post_id,user_id) IN (#{(['(?)'] * tuples.size).join(', ')})",
*tuples
)
.pluck(:post_id, :user_id, "reply_key::text")
.each do |post_id, user_id, key|
reply_keys[[post_id, user_id]] = key
end
end
render_serialized(email_logs, EmailLogSerializer, reply_keys: reply_keys)
end
def skipped
skipped_email_logs = filter_logs(SkippedEmailLog, params)
render_serialized(skipped_email_logs, SkippedEmailLogSerializer)
end
def bounced
email_logs = filter_logs(EmailLog.bounced, params)
render_serialized(email_logs, EmailLogSerializer)
end
def received
incoming_emails = filter_incoming_emails(IncomingEmail, params)
render_serialized(incoming_emails, IncomingEmailSerializer)
end
def rejected
incoming_emails = filter_incoming_emails(IncomingEmail.errored, params)
render_serialized(incoming_emails, IncomingEmailSerializer)
end
def preview_digest
params.require(:last_seen_at)
params.require(:username)
user = User.find_by_username(params[:username])
raise Discourse::InvalidParameters unless user
renderer = Email::Renderer.new(UserNotifications.digest(user, since: params[:last_seen_at]))
render json: MultiJson.dump(html_content: renderer.html, text_content: renderer.text)
end
def advanced_test
params.require(:email)
receiver = Email::Receiver.new(params['email'])
text, elided, format = receiver.select_body
render json: success_json.merge!(
text: text,
elided: elided,
format: format
)
end
def send_digest
params.require(:last_seen_at)
params.require(:username)
params.require(:email)
user = User.find_by_username(params[:username])
message, skip_reason = UserNotifications.public_send(:digest, user,
since: params[:last_seen_at]
)
if message
message.to = params[:email]
begin
Email::Sender.new(message, :digest).send
render json: success_json
rescue => e
render json: { errors: [e.message] }, status: 422
end
else
render json: { errors: skip_reason }
end
end
def smtp_should_reject
params.require(:from)
params.require(:to)
# These strings aren't localized; they are sent to an anonymous SMTP user.
if !User.with_email(Email.downcase(params[:from])).exists? && !SiteSetting.enable_staged_users
render json: { reject: true, reason: "Mail from your address is not accepted. Do you have an account here?" }
elsif Email::Receiver.check_address(Email.downcase(params[:to])).nil?
render json: { reject: true, reason: "Mail to this address is not accepted. Check the address and try to send again?" }
else
render json: { reject: false }
end
end
def handle_mail
deprecated_email_param_used = false
if params[:email_encoded].present?
email_raw = Base64.strict_decode64(params[:email_encoded])
elsif params[:email].present?
deprecated_email_param_used = true
email_raw = params[:email]
else
raise ActionController::ParameterMissing.new("email_encoded or email")
end
retry_count = 0
begin
Jobs.enqueue(:process_email, mail: email_raw, retry_on_rate_limit: true, source: :handle_mail)
rescue JSON::GeneratorError, Encoding::UndefinedConversionError => e
if retry_count == 0
email_raw = email_raw.force_encoding('iso-8859-1').encode("UTF-8")
retry_count += 1
retry
else
raise e
end
end
# TODO: 2022-05-01 Remove this route once all sites have migrated over
# to using the new email_encoded param.
if deprecated_email_param_used
render plain: "warning: the email parameter is deprecated. all POST requests to this route should be sent with a base64 strict encoded encoded_email parameter instead. email has been received and is queued for processing"
else
render plain: "email has been received and is queued for processing"
end
end
def raw_email
params.require(:id)
incoming_email = IncomingEmail.find(params[:id].to_i)
text, html = Email.extract_parts(incoming_email.raw)
render json: { raw_email: incoming_email.raw, text_part: text, html_part: html }
end
def incoming
params.require(:id)
incoming_email = IncomingEmail.find(params[:id].to_i)
serializer = IncomingEmailDetailsSerializer.new(incoming_email, root: false)
render_json_dump(serializer)
end
def incoming_from_bounced
params.require(:id)
begin
email_log = EmailLog.find_by(id: params[:id].to_i, bounced: true)
raise Discourse::InvalidParameters if email_log&.bounce_key.blank?
if Email::Sender.bounceable_reply_address?
bounced_to_address = Email::Sender.bounce_address(email_log.bounce_key)
incoming_email = IncomingEmail.find_by(to_addresses: bounced_to_address)
end
if incoming_email.nil?
email_local_part, email_domain = SiteSetting.notification_email.split('@')
bounced_to_address = "#{email_local_part}+verp-#{email_log.bounce_key}@#{email_domain}"
incoming_email = IncomingEmail.find_by(to_addresses: bounced_to_address)
end
raise Discourse::NotFound if incoming_email.nil?
serializer = IncomingEmailDetailsSerializer.new(incoming_email, root: false)
render_json_dump(serializer)
rescue => e
render json: { errors: [e.message] }, status: 404
end
end
private
def filter_logs(logs, params)
table_name = logs.table_name
logs = logs.includes(:user, post: :topic)
.references(:user)
.order(created_at: :desc)
.offset(params[:offset] || 0)
.limit(50)
logs = logs.where("users.username ILIKE ?", "%#{params[:user]}%") if params[:user].present?
logs = logs.where("#{table_name}.to_address ILIKE ?", "%#{params[:address]}%") if params[:address].present?
logs = logs.where("#{table_name}.email_type ILIKE ?", "%#{params[:type]}%") if params[:type].present?
logs
end
def filter_incoming_emails(incoming_emails, params)
incoming_emails = incoming_emails.includes(:user, post: :topic)
.order(created_at: :desc)
.offset(params[:offset] || 0)
.limit(50)
incoming_emails = incoming_emails.where("from_address ILIKE ?", "%#{params[:from]}%") if params[:from].present?
incoming_emails = incoming_emails.where("to_addresses ILIKE :to OR cc_addresses ILIKE :to", to: "%#{params[:to]}%") if params[:to].present?
incoming_emails = incoming_emails.where("subject ILIKE ?", "%#{params[:subject]}%") if params[:subject].present?
incoming_emails = incoming_emails.where("error ILIKE ?", "%#{params[:error]}%") if params[:error].present?
incoming_emails
end
def delivery_settings
action_mailer_settings
.reject { |k, _| k == :password }
.map { |k, v| { name: k, value: v } }
end
def delivery_method
ActionMailer::Base.delivery_method
end
def action_mailer_settings
ActionMailer::Base.public_send "#{delivery_method}_settings"
end
end