Implemented issue #2092 - Send postmaster email to sender if email is too big
This commit is contained in:
parent
bc38f1c1ae
commit
fae194918e
13 changed files with 661 additions and 31 deletions
|
@ -172,6 +172,7 @@ test:integration:email_helper_deliver:
|
|||
- bundle exec rails test test/integration/email_helper_test.rb
|
||||
- bundle exec rails test test/integration/email_deliver_test.rb
|
||||
- bundle exec rails test test/integration/email_keep_on_server_test.rb
|
||||
- bundle exec rails test test/integration/email_postmaster_to_sender.rb
|
||||
|
||||
test:integration:facebook:
|
||||
<<: *test_integration_definition
|
||||
|
|
|
@ -206,6 +206,7 @@ example
|
|||
count = 0
|
||||
count_fetched = 0
|
||||
count_max = 5000
|
||||
too_large_messages = []
|
||||
active_check_interval = 20
|
||||
notice = ''
|
||||
message_ids.each do |message_id|
|
||||
|
@ -226,13 +227,6 @@ example
|
|||
# ignore verify messages
|
||||
next if !messages_is_too_old_verify?(message_meta, count, count_all)
|
||||
|
||||
# ignore to big messages
|
||||
info = too_big?(message_meta, count, count_all)
|
||||
if info
|
||||
notice += "#{info}\n"
|
||||
next
|
||||
end
|
||||
|
||||
# ignore deleted messages
|
||||
next if deleted?(message_meta, count, count_all)
|
||||
|
||||
|
@ -251,7 +245,24 @@ example
|
|||
end
|
||||
next if !msg
|
||||
|
||||
process(channel, msg, false)
|
||||
# do not process too big messages, instead download & send postmaster reply
|
||||
too_large_info = too_large?(message_meta)
|
||||
if too_large_info
|
||||
if Setting.get('postmaster_send_reject_if_mail_too_large') == true
|
||||
info = " - download message #{count}/#{count_all} - ignore message because it's too large (is:#{too_large_info[0]} MB/max:#{too_large_info[1]} MB)"
|
||||
Rails.logger.info info
|
||||
notice += "#{info}\n"
|
||||
process_oversized_mail(channel, msg)
|
||||
else
|
||||
info = " - ignore message #{count}/#{count_all} - because message is too large (is:#{too_large_info[0]} MB/max:#{too_large_info[1]} MB)"
|
||||
Rails.logger.info info
|
||||
notice += "#{info}\n"
|
||||
too_large_messages.push info
|
||||
next
|
||||
end
|
||||
else
|
||||
process(channel, msg, false)
|
||||
end
|
||||
|
||||
begin
|
||||
timeout(FETCH_MSG_TIMEOUT) do
|
||||
|
@ -282,6 +293,11 @@ example
|
|||
if count.zero?
|
||||
Rails.logger.info ' - no message'
|
||||
end
|
||||
|
||||
if too_large_messages.present?
|
||||
raise too_large_messages.join("\n")
|
||||
end
|
||||
|
||||
Rails.logger.info 'done'
|
||||
{
|
||||
result: 'ok',
|
||||
|
@ -420,7 +436,7 @@ returns
|
|||
|
||||
check if email is to big
|
||||
|
||||
Channel::Driver::IMAP.too_big?(message_meta, count, count_all)
|
||||
Channel::Driver::IMAP.too_large?(message_meta, count, count_all)
|
||||
|
||||
returns
|
||||
|
||||
|
@ -428,14 +444,13 @@ returns
|
|||
|
||||
=end
|
||||
|
||||
def too_big?(message_meta, count, count_all)
|
||||
def too_large?(message_meta)
|
||||
max_message_size = Setting.get('postmaster_max_size').to_f
|
||||
real_message_size = message_meta.attr['RFC822.SIZE'].to_f / 1024 / 1024
|
||||
if real_message_size > max_message_size
|
||||
info = " - ignore message #{count}/#{count_all} - because message is too big (is:#{real_message_size} MB/max:#{max_message_size} MB)"
|
||||
Rails.logger.info info
|
||||
return info
|
||||
return [real_message_size, max_message_size]
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
|
|
|
@ -136,6 +136,7 @@ returns
|
|||
count_all = mails.size
|
||||
count = 0
|
||||
count_fetched = 0
|
||||
too_large_messages = []
|
||||
active_check_interval = 20
|
||||
notice = ''
|
||||
mails.first(2000).each do |m|
|
||||
|
@ -165,18 +166,28 @@ returns
|
|||
end
|
||||
end
|
||||
|
||||
# ignore to big messages
|
||||
# do not process too large messages, instead download and send postmaster reply
|
||||
max_message_size = Setting.get('postmaster_max_size').to_f
|
||||
real_message_size = mail.size.to_f / 1024 / 1024
|
||||
if real_message_size > max_message_size
|
||||
info = " - ignore message #{count}/#{count_all} - because message is too big (is:#{real_message_size} MB/max:#{max_message_size} MB)"
|
||||
Rails.logger.info info
|
||||
notice += "#{info}\n"
|
||||
next
|
||||
end
|
||||
if Setting.get('postmaster_send_reject_if_mail_too_large') == true
|
||||
info = " - download message #{count}/#{count_all} - ignore message because it's too large (is:#{real_message_size} MB/max:#{max_message_size} MB)"
|
||||
Rails.logger.info info
|
||||
notice += "#{info}\n"
|
||||
process_oversized_mail(channel, mail)
|
||||
else
|
||||
info = " - ignore message #{count}/#{count_all} - because message is too large (is:#{real_message_size} MB/max:#{max_message_size} MB)"
|
||||
Rails.logger.info info
|
||||
notice += "#{info}\n"
|
||||
too_large_messages.push info
|
||||
next
|
||||
end
|
||||
|
||||
# delete email from server after article was created
|
||||
process(channel, m.pop, false)
|
||||
else
|
||||
process(channel, m.pop, false)
|
||||
end
|
||||
|
||||
m.delete
|
||||
count_fetched += 1
|
||||
end
|
||||
|
@ -184,6 +195,11 @@ returns
|
|||
if count.zero?
|
||||
Rails.logger.info ' - no message'
|
||||
end
|
||||
|
||||
if too_large_messages.present?
|
||||
raise too_large_messages.join("\n")
|
||||
end
|
||||
|
||||
Rails.logger.info 'done'
|
||||
{
|
||||
result: 'ok',
|
||||
|
|
|
@ -116,18 +116,15 @@ returns
|
|||
end
|
||||
rescue => e
|
||||
# store unprocessable email for bug reporting
|
||||
path = Rails.root.join('tmp', 'unprocessable_mail')
|
||||
FileUtils.mkpath path
|
||||
md5 = Digest::MD5.hexdigest(msg)
|
||||
filename = "#{path}/#{md5}.eml"
|
||||
filename = archive_mail('unprocessable_mail', msg)
|
||||
|
||||
message = "ERROR: Can't process email, you will find it for bug reporting under #{filename}, please create an issue at https://github.com/zammad/zammad/issues"
|
||||
|
||||
p message # rubocop:disable Rails/Output
|
||||
p 'ERROR: ' + e.inspect # rubocop:disable Rails/Output
|
||||
Rails.logger.error message
|
||||
Rails.logger.error e
|
||||
File.open(filename, 'wb') do |file|
|
||||
file.write msg
|
||||
end
|
||||
|
||||
return false if exception == false
|
||||
|
||||
raise e.inspect + "\n" + e.backtrace.join("\n")
|
||||
|
@ -486,6 +483,19 @@ process unprocessable_mails (tmp/unprocessable_mail/*.eml) again
|
|||
files
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
process oversized emails by:
|
||||
1. Archiving the oversized mail as tmp/oversized_mail/timestamp_md5.eml
|
||||
2. Reply with a postmaster message to inform the sender
|
||||
|
||||
=end
|
||||
|
||||
def process_oversized_mail(channel, msg)
|
||||
archive_mail('oversized_mail', msg)
|
||||
postmaster_response(channel, msg)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message_header_hash(mail)
|
||||
|
@ -783,6 +793,70 @@ process unprocessable_mails (tmp/unprocessable_mail/*.eml) again
|
|||
|
||||
[attach]
|
||||
end
|
||||
|
||||
# Archive the given message as tmp/folder/timestamp_md5.eml
|
||||
def archive_mail(folder, msg)
|
||||
path = Rails.root.join('tmp', folder)
|
||||
FileUtils.mkpath path
|
||||
|
||||
# MD5 hash the msg and save it as "timestamp_md5.eml"
|
||||
md5 = Digest::MD5.hexdigest(msg)
|
||||
filename = "#{Time.zone.now.iso8601}_#{md5}.eml"
|
||||
file_path = Rails.root.join('tmp', folder, filename)
|
||||
|
||||
File.open(file_path, 'wb') do |file|
|
||||
file.write msg
|
||||
end
|
||||
|
||||
file_path
|
||||
end
|
||||
|
||||
# Auto reply as the postmaster to oversized emails with:
|
||||
# [ALERT] Message too large
|
||||
def postmaster_response(channel, msg)
|
||||
begin
|
||||
reply_mail = compose_postmaster_reply(msg)
|
||||
rescue NotificationFactory::FileNotFoundError => e
|
||||
Rails.logger.error 'No valid postmaster email_oversized template found. Skipping postmaster reply. ' + e.inspect
|
||||
return
|
||||
end
|
||||
|
||||
Rails.logger.error "Send mail too large postmaster message to: #{reply_mail[:to]}"
|
||||
reply_mail[:from] = EmailAddress.find_by(channel: channel).email
|
||||
channel.deliver(reply_mail)
|
||||
rescue => e
|
||||
Rails.logger.error "Error during sending of postmaster oversized email auto-reply: #{e.inspect}\n#{e.backtrace}"
|
||||
end
|
||||
|
||||
# Compose a "Message too large" reply to the given message
|
||||
def compose_postmaster_reply(raw_incoming_mail, locale = nil)
|
||||
parsed_incoming_mail = Channel::EmailParser.new.parse(raw_incoming_mail)
|
||||
|
||||
# construct a dummy mail object
|
||||
mail = OpenStruct.new
|
||||
mail.from_display_name = parsed_incoming_mail[:from_display_name]
|
||||
mail.subject = parsed_incoming_mail[:subject]
|
||||
mail.msg_size = format('%.2f', raw_incoming_mail.size.to_f / 1024 / 1024)
|
||||
|
||||
reply = NotificationFactory::Mailer.template(
|
||||
template: 'email_oversized',
|
||||
locale: locale,
|
||||
format: 'txt',
|
||||
objects: {
|
||||
mail: mail,
|
||||
},
|
||||
raw: true, # will not add application template
|
||||
standalone: true, # default: false - will send header & footer
|
||||
)
|
||||
|
||||
reply.merge(
|
||||
to: parsed_incoming_mail[:from_email],
|
||||
body: reply[:body].gsub(/\n/, "\r\n"),
|
||||
content_type: 'text/plain',
|
||||
References: parsed_incoming_mail[:message_id],
|
||||
'In-Reply-To': parsed_incoming_mail[:message_id],
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
module Mail
|
||||
|
|
12
app/views/mailer/email_oversized/de.txt.erb
Normal file
12
app/views/mailer/email_oversized/de.txt.erb
Normal file
|
@ -0,0 +1,12 @@
|
|||
[Unzustellbar] Nachricht zu groß
|
||||
Hallo #{mail.from_display_name},
|
||||
|
||||
Ihre E-Mail mit dem Betreff "#{mail.subject}" konnte nicht an einen oder mehrere Empfänger zugestellt werden.
|
||||
|
||||
Die Nachricht hatte eine Größe von #{mail.msg_size} MB, wir akzeptieren jedoch nur E-Mails mit einer Größe von bis zu #{config.postmaster_max_size} MB.
|
||||
|
||||
Bitte reduzieren Sie die Größe Ihrer Nachricht und versuchen Sie es erneut. Vielen Dank für Ihr Verständnis.
|
||||
|
||||
Mit freundlichen Grüßen
|
||||
|
||||
Postmaster von #{config.fqdn}
|
12
app/views/mailer/email_oversized/en.txt.erb
Normal file
12
app/views/mailer/email_oversized/en.txt.erb
Normal file
|
@ -0,0 +1,12 @@
|
|||
[ALERT] Message too large
|
||||
Dear #{mail.from_display_name},
|
||||
|
||||
Unfortunately your email titled "#{mail.subject}" could not be delivered to one or more recipients.
|
||||
|
||||
Your message was #{mail.msg_size} MB but we only accept messages up to #{config.postmaster_max_size} MB.
|
||||
|
||||
Please reduce the message size and try again. Thank you for your understanding.
|
||||
|
||||
Regretfully,
|
||||
|
||||
Postmaster of #{config.fqdn}
|
|
@ -0,0 +1,34 @@
|
|||
class SettingPostmasterSendRejectEmail < ActiveRecord::Migration[5.2]
|
||||
def up
|
||||
|
||||
# return if it's a new setup
|
||||
return if !Setting.find_by(name: 'system_init_done')
|
||||
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Send postmaster mail if mail too large',
|
||||
name: 'postmaster_send_reject_if_mail_too_large',
|
||||
area: 'Email::Base',
|
||||
description: 'Send postmaster reject mail to sender of mail if mail is too large.',
|
||||
options: {
|
||||
form: [
|
||||
{
|
||||
display: '',
|
||||
null: true,
|
||||
name: 'postmaster_send_reject_if_mail_too_large',
|
||||
tag: 'boolean',
|
||||
options: {
|
||||
true => 'yes',
|
||||
false => 'no',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
state: true,
|
||||
preferences: {
|
||||
online_service_disable: true,
|
||||
permission: ['admin.channel_email'],
|
||||
},
|
||||
frontend: false
|
||||
)
|
||||
end
|
||||
end
|
|
@ -2582,6 +2582,33 @@ Setting.create_if_not_exists(
|
|||
frontend: false
|
||||
)
|
||||
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Send postmaster mail if mail too large',
|
||||
name: 'postmaster_send_reject_if_mail_too_large',
|
||||
area: 'Email::Base',
|
||||
description: 'Send postmaster reject mail to sender of mail if mail is too large.',
|
||||
options: {
|
||||
form: [
|
||||
{
|
||||
display: '',
|
||||
null: true,
|
||||
name: 'postmaster_send_reject_if_mail_too_large',
|
||||
tag: 'boolean',
|
||||
options: {
|
||||
true => 'yes',
|
||||
false => 'no',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
state: true,
|
||||
preferences: {
|
||||
online_service_disable: true,
|
||||
permission: ['admin.channel_email'],
|
||||
},
|
||||
frontend: false
|
||||
)
|
||||
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Notification Sender',
|
||||
name: 'notification_sender',
|
||||
|
|
|
@ -29,17 +29,26 @@ returns
|
|||
|
||||
=end
|
||||
|
||||
class FileNotFoundError < StandardError; end
|
||||
|
||||
def self.template_read(data)
|
||||
template = File.readlines(template_path(data))
|
||||
template_path = template_path(data)
|
||||
|
||||
template = File.readlines(template_path)
|
||||
|
||||
{ subject: template.shift, body: template.join }
|
||||
end
|
||||
|
||||
def self.template_path(data)
|
||||
template_filenames(data)
|
||||
candidates = template_filenames(data)
|
||||
.map { |filename| data.merge(filename: filename) }
|
||||
.map { |data_hash| TEMPLATE_PATH_STRING % data_hash }
|
||||
.find(&File.method(:exist?))
|
||||
|
||||
found = candidates.find(&File.method(:exist?))
|
||||
|
||||
raise FileNotFoundError, "Missing template files #{candidates}!" if !found
|
||||
|
||||
found
|
||||
end
|
||||
private_class_method :template_path
|
||||
|
||||
|
|
|
@ -281,7 +281,7 @@ returns
|
|||
template = NotificationFactory.template_read(
|
||||
locale: data[:locale] || Setting.get('locale_default') || 'en-us',
|
||||
template: data[:template],
|
||||
format: 'html',
|
||||
format: data[:format] || 'html',
|
||||
type: 'mailer',
|
||||
)
|
||||
|
||||
|
@ -292,6 +292,10 @@ returns
|
|||
template: template[:subject],
|
||||
escape: false
|
||||
).render
|
||||
|
||||
# strip off the extra newline at the end for the subjects of plaintext templates
|
||||
message_subject.chomp! if data[:format] == 'txt'
|
||||
|
||||
message_body = NotificationFactory::Renderer.new(
|
||||
objects: data[:objects],
|
||||
locale: data[:locale],
|
||||
|
|
93
spec/lib/notification_factory/mailer_spec.rb
Normal file
93
spec/lib/notification_factory/mailer_spec.rb
Normal file
|
@ -0,0 +1,93 @@
|
|||
require 'rails_helper'
|
||||
require 'ostruct'
|
||||
|
||||
RSpec.describe NotificationFactory::Mailer do
|
||||
describe '#template' do
|
||||
context 'for postmaster oversized mail' do
|
||||
let(:raw_incoming_mail) { File.read(Rails.root.join('test', 'data', 'mail', 'mail010.box')) }
|
||||
|
||||
let(:parsed_incoming_mail) { Channel::EmailParser.new.parse raw_incoming_mail }
|
||||
|
||||
let(:incoming_mail) do
|
||||
mail = OpenStruct.new
|
||||
mail.from_display_name = parsed_incoming_mail[:from_display_name]
|
||||
mail.subject = parsed_incoming_mail[:subject]
|
||||
mail.msg_size = format('%.2f', raw_incoming_mail.size.to_f / 1024 / 1024)
|
||||
mail
|
||||
end
|
||||
|
||||
let(:en_expected_subject) { '[ALERT] Message too large' }
|
||||
|
||||
let(:en_expected_body) do
|
||||
<<~BODY
|
||||
Dear Smith Sepp,
|
||||
|
||||
Unfortunately your email titled \"Gruß aus Oberalteich\" could not be delivered to one or more recipients.
|
||||
|
||||
Your message was 0.01 MB but we only accept messages up to 10 MB.
|
||||
|
||||
Please reduce the message size and try again. Thank you for your understanding.
|
||||
|
||||
Regretfully,
|
||||
|
||||
Postmaster of zammad.example.com
|
||||
BODY
|
||||
end
|
||||
|
||||
shared_examples 'plaintext mail templating' do
|
||||
it 'templates correctly' do
|
||||
result = described_class.template(
|
||||
template: 'email_oversized',
|
||||
locale: locale,
|
||||
format: 'txt',
|
||||
objects: {
|
||||
mail: incoming_mail,
|
||||
},
|
||||
raw: true, # will not add application template
|
||||
standalone: true, # default: false - will send header & footer
|
||||
)
|
||||
expect(result[:subject]).to eq(expected_subject)
|
||||
expect(result[:body]).to eq(expected_body)
|
||||
end
|
||||
end
|
||||
|
||||
context 'English locale (en)' do
|
||||
include_examples 'plaintext mail templating' do
|
||||
let(:locale) { 'en' }
|
||||
let(:expected_subject) { en_expected_subject }
|
||||
let(:expected_body) { en_expected_body }
|
||||
end
|
||||
end
|
||||
|
||||
context 'German locale (de)' do
|
||||
include_examples 'plaintext mail templating' do
|
||||
let(:locale) { 'de' }
|
||||
let(:expected_subject) { '[Unzustellbar] Nachricht zu groß' }
|
||||
let(:expected_body) do
|
||||
<<~BODY
|
||||
Hallo Smith Sepp,
|
||||
|
||||
Ihre E-Mail mit dem Betreff \"Gruß aus Oberalteich\" konnte nicht an einen oder mehrere Empfänger zugestellt werden.
|
||||
|
||||
Die Nachricht hatte eine Größe von 0.01 MB, wir akzeptieren jedoch nur E-Mails mit einer Größe von bis zu 10 MB.
|
||||
|
||||
Bitte reduzieren Sie die Größe Ihrer Nachricht und versuchen Sie es erneut. Vielen Dank für Ihr Verständnis.
|
||||
|
||||
Mit freundlichen Grüßen
|
||||
|
||||
Postmaster von zammad.example.com
|
||||
BODY
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'unsupported locale, which defaults back to English locale (en)' do
|
||||
include_examples 'plaintext mail templating' do
|
||||
let(:locale) { 'UNSUPPORTED_LOCALE' }
|
||||
let(:expected_subject) { en_expected_subject }
|
||||
let(:expected_body) { en_expected_body }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -997,4 +997,124 @@ RSpec.describe Channel::EmailParser, type: :model do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#compose_postmaster_reply' do
|
||||
let(:raw_incoming_mail) { File.read(Rails.root.join('test', 'data', 'mail', 'mail010.box')) }
|
||||
|
||||
shared_examples 'postmaster reply' do
|
||||
it 'composes postmaster reply' do
|
||||
reply = Channel::EmailParser.new.send(:compose_postmaster_reply, raw_incoming_mail, locale)
|
||||
expect(reply[:to]).to eq('smith@example.com')
|
||||
expect(reply[:content_type]).to eq('text/plain')
|
||||
expect(reply[:subject]).to eq(expected_subject)
|
||||
expect(reply[:body]).to eq(expected_body)
|
||||
end
|
||||
end
|
||||
|
||||
context 'for English locale (en)' do
|
||||
include_examples 'postmaster reply' do
|
||||
let(:locale) { 'en' }
|
||||
let(:expected_subject) { '[ALERT] Message too large' }
|
||||
let(:expected_body) do
|
||||
body = <<~BODY
|
||||
Dear Smith Sepp,
|
||||
|
||||
Unfortunately your email titled \"Gruß aus Oberalteich\" could not be delivered to one or more recipients.
|
||||
|
||||
Your message was 0.01 MB but we only accept messages up to 10 MB.
|
||||
|
||||
Please reduce the message size and try again. Thank you for your understanding.
|
||||
|
||||
Regretfully,
|
||||
|
||||
Postmaster of zammad.example.com
|
||||
BODY
|
||||
body.gsub(/\n/, "\r\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for German locale (de)' do
|
||||
include_examples 'postmaster reply' do
|
||||
let(:locale) { 'de' }
|
||||
let(:expected_subject) { '[Unzustellbar] Nachricht zu groß' }
|
||||
let(:expected_body) do
|
||||
body = <<~BODY
|
||||
Hallo Smith Sepp,
|
||||
|
||||
Ihre E-Mail mit dem Betreff \"Gruß aus Oberalteich\" konnte nicht an einen oder mehrere Empfänger zugestellt werden.
|
||||
|
||||
Die Nachricht hatte eine Größe von 0.01 MB, wir akzeptieren jedoch nur E-Mails mit einer Größe von bis zu 10 MB.
|
||||
|
||||
Bitte reduzieren Sie die Größe Ihrer Nachricht und versuchen Sie es erneut. Vielen Dank für Ihr Verständnis.
|
||||
|
||||
Mit freundlichen Grüßen
|
||||
|
||||
Postmaster von zammad.example.com
|
||||
BODY
|
||||
body.gsub(/\n/, "\r\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#compose_postmaster_reply' do
|
||||
let(:raw_incoming_mail) { File.read(Rails.root.join('test', 'data', 'mail', 'mail010.box')) }
|
||||
|
||||
shared_examples 'postmaster reply' do
|
||||
it 'composes postmaster reply' do
|
||||
reply = Channel::EmailParser.new.send(:compose_postmaster_reply, raw_incoming_mail, locale)
|
||||
expect(reply[:to]).to eq('smith@example.com')
|
||||
expect(reply[:content_type]).to eq('text/plain')
|
||||
expect(reply[:subject]).to eq(expected_subject)
|
||||
expect(reply[:body]).to eq(expected_body)
|
||||
end
|
||||
end
|
||||
|
||||
context 'for English locale (en)' do
|
||||
include_examples 'postmaster reply' do
|
||||
let(:locale) { 'en' }
|
||||
let(:expected_subject) { '[ALERT] Message too large' }
|
||||
let(:expected_body) do
|
||||
body = <<~BODY
|
||||
Dear Smith Sepp,
|
||||
|
||||
Unfortunately your email titled \"Gruß aus Oberalteich\" could not be delivered to one or more recipients.
|
||||
|
||||
Your message was 0.01 MB but we only accept messages up to 10 MB.
|
||||
|
||||
Please reduce the message size and try again. Thank you for your understanding.
|
||||
|
||||
Regretfully,
|
||||
|
||||
Postmaster of zammad.example.com
|
||||
BODY
|
||||
body.gsub(/\n/, "\r\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for German locale (de)' do
|
||||
include_examples 'postmaster reply' do
|
||||
let(:locale) { 'de' }
|
||||
let(:expected_subject) { '[Unzustellbar] Nachricht zu groß' }
|
||||
let(:expected_body) do
|
||||
body = <<~BODY
|
||||
Hallo Smith Sepp,
|
||||
|
||||
Ihre E-Mail mit dem Betreff \"Gruß aus Oberalteich\" konnte nicht an einen oder mehrere Empfänger zugestellt werden.
|
||||
|
||||
Die Nachricht hatte eine Größe von 0.01 MB, wir akzeptieren jedoch nur E-Mails mit einer Größe von bis zu 10 MB.
|
||||
|
||||
Bitte reduzieren Sie die Größe Ihrer Nachricht und versuchen Sie es erneut. Vielen Dank für Ihr Verständnis.
|
||||
|
||||
Mit freundlichen Grüßen
|
||||
|
||||
Postmaster von zammad.example.com
|
||||
BODY
|
||||
body.gsub(/\n/, "\r\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
213
test/integration/email_postmaster_to_sender.rb
Normal file
213
test/integration/email_postmaster_to_sender.rb
Normal file
|
@ -0,0 +1,213 @@
|
|||
require 'test_helper'
|
||||
require 'net/imap'
|
||||
|
||||
class EmailPostmasterToSender < ActiveSupport::TestCase
|
||||
|
||||
setup do
|
||||
Setting.set('postmaster_max_size', 0.1)
|
||||
|
||||
@test_id = rand(999_999_999)
|
||||
|
||||
# setup the IMAP account info for Zammad
|
||||
if ENV['MAIL_SERVER'].blank?
|
||||
raise "Need MAIL_SERVER as ENV variable like export MAIL_SERVER='mx.example.com'"
|
||||
end
|
||||
if ENV['MAIL_SERVER_ACCOUNT'].blank?
|
||||
raise "Need MAIL_SERVER_ACCOUNT as ENV variable like export MAIL_SERVER_ACCOUNT='user:somepass'"
|
||||
end
|
||||
|
||||
@server_address = ENV['MAIL_SERVER']
|
||||
@server_login = ENV['MAIL_SERVER_ACCOUNT'].split(':')[0]
|
||||
@server_password = ENV['MAIL_SERVER_ACCOUNT'].split(':')[1]
|
||||
|
||||
@folder = "postmaster_to_sender_#{@test_id}"
|
||||
|
||||
if ENV['MAIL_SERVER_EMAIL'].blank?
|
||||
raise "Need MAIL_SERVER_EMAIL as ENV variable like export MAIL_SERVER_EMAIL='master@example.com'"
|
||||
end
|
||||
|
||||
@sender_email_address = ENV['MAIL_SERVER_EMAIL']
|
||||
|
||||
@email_address = EmailAddress.create!(
|
||||
realname: 'me Helpdesk',
|
||||
email: "me#{@test_id}@example.com",
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
|
||||
group = Group.create_or_update(
|
||||
name: 'PostmasterToSenderTest',
|
||||
email_address_id: @email_address.id,
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
|
||||
@channel = Channel.create!(
|
||||
area: 'Email::Account',
|
||||
group_id: group.id,
|
||||
options: {
|
||||
inbound: {
|
||||
adapter: 'imap',
|
||||
options: {
|
||||
host: @server_address,
|
||||
user: @server_login,
|
||||
password: @server_password,
|
||||
ssl: true,
|
||||
folder: @folder,
|
||||
keep_on_server: false,
|
||||
}
|
||||
},
|
||||
outbound: {
|
||||
adapter: 'smtp',
|
||||
options: {
|
||||
host: @server_address,
|
||||
port: 25,
|
||||
start_tls: true,
|
||||
user: @server_login,
|
||||
password: @server_password,
|
||||
email: @email_address.email
|
||||
},
|
||||
},
|
||||
},
|
||||
active: true,
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
@email_address.channel_id = @channel.id
|
||||
@email_address.save!
|
||||
end
|
||||
|
||||
test 'postmaster reply with email on oversized incoming emails' do
|
||||
imap = Net::IMAP.new(@server_address, 993, true, nil, false)
|
||||
imap.login(@server_login, @server_password)
|
||||
imap.create(@folder)
|
||||
imap.select(@folder)
|
||||
|
||||
# put a very large message in it
|
||||
large_message = "Subject: Oversized Email Message
|
||||
From: Max Mustermann <#{@sender_email_address}>
|
||||
To: shugo@example.com
|
||||
Message-ID: <#{@test_id}@zammad.test.com>
|
||||
|
||||
Oversized Email Message Body #{'#' * 120_000}
|
||||
".gsub(/\n/, "\r\n")
|
||||
|
||||
large_message_md5 = Digest::MD5.hexdigest(large_message)
|
||||
large_message_size = format('%.2f', large_message.size.to_f / 1024 / 1024)
|
||||
|
||||
imap.append(@folder, large_message, [], Time.zone.now)
|
||||
|
||||
@channel.fetch(true)
|
||||
|
||||
# 1. verify that the oversized email has been saved locally to:
|
||||
# /tmp/oversized_mail/yyyy-mm-ddThh:mm:ss-:md5.eml
|
||||
path = Rails.root.join('tmp', 'oversized_mail')
|
||||
target_files = Dir.entries(path).select do |filename|
|
||||
filename =~ /^.+_#{large_message_md5}\.eml$/
|
||||
end
|
||||
assert(target_files.present?, 'Large message .eml log file must be present.')
|
||||
|
||||
# pick the latest file that matches the criteria
|
||||
target_file = target_files.max
|
||||
|
||||
# verify that the file is byte for byte identical to the sent message
|
||||
file_path = Rails.root.join('tmp', 'oversized_mail', target_file)
|
||||
eml_data = File.read(file_path)
|
||||
assert_equal(large_message, eml_data)
|
||||
|
||||
# 2. verify that a postmaster response email has been sent to the sender
|
||||
imap.select('inbox')
|
||||
message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
|
||||
assert(message_ids.count.positive?, 'Must have received a reply from the postmaster')
|
||||
imap_message_id = message_ids.last
|
||||
msg = imap.fetch(imap_message_id, 'RFC822')[0].attr['RFC822']
|
||||
assert(msg.present?, 'Must have received a reply from the postmaster')
|
||||
imap.store(imap_message_id, '+FLAGS', [:Deleted])
|
||||
imap.expunge()
|
||||
|
||||
# parse the reply mail and verify the various headers
|
||||
parser = Channel::EmailParser.new
|
||||
mail = parser.parse(msg)
|
||||
assert_equal(mail[:from_email], @email_address.email)
|
||||
assert_equal(mail[:subject], '[ALERT] Message too large')
|
||||
assert_equal("<#{@test_id}@zammad.test.com>",
|
||||
mail['references'],
|
||||
'Reply\'s Referecnes header must match the send message ID')
|
||||
assert_equal("<#{@test_id}@zammad.test.com>",
|
||||
mail['in-reply-to'],
|
||||
'Reply\'s In-Reply-To header must match the send message ID')
|
||||
|
||||
# verify the reply mail body content
|
||||
body = mail[:body]
|
||||
assert(body.start_with?('Dear Max Mustermann'), 'Body must contain sender name')
|
||||
assert(body.include?('Oversized Email Message'), 'Body must contain original subject')
|
||||
assert(body.include?('0.1 MB'), 'Body must contain max allowed message size')
|
||||
assert(body.include?("#{large_message_size} MB"), 'Body must contain the original message size')
|
||||
assert(body.include?(Setting.get('fqdn')), 'Body must contain the Zammad instance name')
|
||||
|
||||
# 3. check if original mail got removed
|
||||
imap.select(@folder)
|
||||
message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
|
||||
assert_equal(message_ids.count, 0, 'Original customer mail must be deleted.')
|
||||
|
||||
# final clean up
|
||||
imap.delete(@folder)
|
||||
@channel.destroy!
|
||||
end
|
||||
|
||||
test 'postmaster reply with no email on oversized incoming emails' do
|
||||
Setting.set('postmaster_send_reject_if_mail_too_large', false)
|
||||
imap = Net::IMAP.new(@server_address, 993, true, nil, false)
|
||||
imap.login(@server_login, @server_password)
|
||||
|
||||
imap.select('inbox')
|
||||
message_count = imap.sort(['DATE'], ['ALL'], 'US-ASCII').count
|
||||
|
||||
imap.create(@folder)
|
||||
imap.select(@folder)
|
||||
|
||||
# put a very large message in it
|
||||
large_message = "Subject: Oversized Email Message
|
||||
From: Max Mustermann <#{@sender_email_address}>
|
||||
To: shugo@example.com
|
||||
Message-ID: <#{@test_id}@zammad.test.com>
|
||||
|
||||
Oversized Email Message Body #{'#' * 120_000}
|
||||
".gsub(/\n/, "\r\n")
|
||||
|
||||
imap.append(@folder, large_message, [], Time.zone.now)
|
||||
|
||||
@channel.fetch(true)
|
||||
|
||||
# 1. verify that the oversized email has been saved locally to:
|
||||
# /tmp/oversized_mail/yyyy-mm-ddThh:mm:ss-:md5.eml
|
||||
path = Rails.root.join('tmp', 'oversized_mail')
|
||||
target_files = Dir.entries(path).select do |filename|
|
||||
filename =~ /^.+?\.eml$/
|
||||
end
|
||||
assert_not(target_files.blank?, 'Large message .eml log file must be blank.')
|
||||
|
||||
# 2. verify that a postmaster response email has been sent to the sender
|
||||
imap.select('inbox')
|
||||
message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
|
||||
assert_equal(message_ids.count, message_count, 'Must not have received a reply from the postmaster')
|
||||
|
||||
# 3. check if original mail got removed
|
||||
imap.select(@folder)
|
||||
message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
|
||||
imap_message_id = message_ids.last
|
||||
msg = imap.fetch(imap_message_id, 'RFC822')[0].attr['RFC822']
|
||||
imap.store(imap_message_id, '+FLAGS', [:Deleted])
|
||||
imap.expunge()
|
||||
assert(msg.present?, 'Oversized Email Message')
|
||||
assert_equal(message_ids.count, 1, 'Original customer mail must be deleted.')
|
||||
|
||||
# final clean up
|
||||
imap.delete(@folder)
|
||||
@channel.destroy!
|
||||
end
|
||||
|
||||
teardown do
|
||||
Setting.set('postmaster_max_size', 10)
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue