FEATURE: new 'enable_forwarded_email' site setting

This commit is contained in:
Régis Hanol 2016-11-16 19:42:11 +01:00
parent ece5442c54
commit 17f2be9f88
5 changed files with 93 additions and 20 deletions

View File

@ -66,7 +66,7 @@ gem 'aws-sdk', require: false
gem 'excon', require: false gem 'excon', require: false
gem 'unf', require: false gem 'unf', require: false
gem 'email_reply_trimmer', '0.1.4' gem 'email_reply_trimmer', '0.1.5'
# note: for image_optim to correctly work you need to follow # note: for image_optim to correctly work you need to follow
# https://github.com/toy/image_optim # https://github.com/toy/image_optim

View File

@ -80,7 +80,7 @@ GEM
docile (1.1.5) docile (1.1.5)
domain_name (0.5.25) domain_name (0.5.25)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
email_reply_trimmer (0.1.4) email_reply_trimmer (0.1.5)
ember-data-source (1.0.0.beta.16.1) ember-data-source (1.0.0.beta.16.1)
ember-source (~> 1.8) ember-source (~> 1.8)
ember-handlebars-template (0.7.3) ember-handlebars-template (0.7.3)
@ -411,7 +411,7 @@ DEPENDENCIES
certified certified
discourse-qunit-rails discourse-qunit-rails
discourse_fastimage (= 2.0.3) discourse_fastimage (= 2.0.3)
email_reply_trimmer (= 0.1.4) email_reply_trimmer (= 0.1.5)
ember-rails (= 0.18.5) ember-rails (= 0.18.5)
ember-source (= 1.12.2) ember-source (= 1.12.2)
excon excon

View File

@ -1246,6 +1246,8 @@ en:
attachment_content_type_blacklist: "List of keywords used to blacklist attachments based on the content type." attachment_content_type_blacklist: "List of keywords used to blacklist attachments based on the content type."
attachment_filename_blacklist: "List of keywords used to blacklist attachments based on the filename." attachment_filename_blacklist: "List of keywords used to blacklist attachments based on the filename."
enable_forwarded_emails: "[BETA] Allow users to create a topic by forwarding an email in."
manual_polling_enabled: "Push emails using the API for email replies." manual_polling_enabled: "Push emails using the API for email replies."
pop3_polling_enabled: "Poll via POP3 for email replies." pop3_polling_enabled: "Poll via POP3 for email replies."
pop3_polling_ssl: "Use SSL while connecting to the POP3 server. (Recommended)" pop3_polling_ssl: "Use SSL while connecting to the POP3 server. (Recommended)"

View File

@ -670,7 +670,7 @@ email:
attachment_filename_blacklist: attachment_filename_blacklist:
type: list type: list
default: "smime.p7s|signature.asc" default: "smime.p7s|signature.asc"
enable_forwarded_emails: false
files: files:
max_image_size_kb: max_image_size_kb:

View File

@ -38,7 +38,7 @@ module Email
def process! def process!
return if is_blacklisted? return if is_blacklisted?
@from_email, @from_display_name = parse_from_field @from_email, @from_display_name = parse_from_field(@mail)
@incoming_email = find_or_create_incoming_email @incoming_email = find_or_create_incoming_email
process_internal process_internal
rescue => e rescue => e
@ -74,7 +74,7 @@ module Email
raise InactiveUserError if !user.active && !user.staged raise InactiveUserError if !user.active && !user.staged
raise BlockedUserError if user.blocked raise BlockedUserError if user.blocked
body, @elided = select_body body, elided = select_body
body ||= "" body ||= ""
raise NoBodyDetectedError if body.blank? && attachments.empty? raise NoBodyDetectedError if body.blank? && attachments.empty?
@ -90,6 +90,7 @@ module Email
elsif post = find_related_post elsif post = find_related_post
create_reply(user: user, create_reply(user: user,
raw: body, raw: body,
elided: elided,
post: post, post: post,
topic: post.topic, topic: post.topic,
skip_validations: user.staged?) skip_validations: user.staged?)
@ -98,7 +99,7 @@ module Email
destinations.each do |destination| destinations.each do |destination|
begin begin
process_destination(destination, user, body) process_destination(destination, user, body, elided)
rescue => e rescue => e
first_exception ||= e first_exception ||= e
else else
@ -237,16 +238,19 @@ module Email
reply.split(previous_replies_regex)[0] reply.split(previous_replies_regex)[0]
end end
def parse_from_field def parse_from_field(mail)
if @mail[:from].errors.blank? if mail[:from].errors.blank?
address_field = @mail[:from].address_list.addresses.first mail[:from].address_list.addresses.each do |address_field|
address_field.decoded address_field.decoded
from_address = address_field.address from_address = address_field.address
from_display_name = address_field.display_name.try(:to_s) from_display_name = address_field.display_name.try(:to_s)
else return [from_address.downcase, from_display_name] if from_address["@"]
from_address = @mail.from[/<([^>]+)>/, 1]
from_display_name = @mail.from[/^([^<]+)/, 1]
end end
end
from_address = mail.from[/<([^>]+)>/, 1]
from_display_name = mail.from[/^([^<]+)/, 1]
[from_address.downcase, from_display_name] [from_address.downcase, from_display_name]
end end
@ -314,12 +318,17 @@ module Email
end end
end end
def process_destination(destination, user, body) def process_destination(destination, user, body, elided)
return if SiteSetting.enable_forwarded_emails &&
has_been_forwarded? &&
process_forwarded_email(destination, user)
case destination[:type] case destination[:type]
when :group when :group
group = destination[:obj] group = destination[:obj]
create_topic(user: user, create_topic(user: user,
raw: body, raw: body,
elided: elided,
title: subject, title: subject,
archetype: Archetype.private_message, archetype: Archetype.private_message,
target_group_names: [group.name], target_group_names: [group.name],
@ -347,11 +356,69 @@ module Email
create_reply(user: user, create_reply(user: user,
raw: body, raw: body,
elided: elided,
post: email_log.post, post: email_log.post,
topic: email_log.post.topic) topic: email_log.post.topic)
end end
end end
def has_been_forwarded?
subject[/^[[:blank]]*(re|fwd?)[[:blank]]?:/i] && embedded_email_raw.present?
end
def embedded_email_raw
return @embedded_email_raw if @embedded_email_raw
text = fix_charset(@mail.multipart? ? @mail.text_part : @mail)
@embedded_email_raw, @before_embedded = EmailReplyTrimmer.extract_embedded_email(text)
@embedded_email_raw
end
def process_forwarded_email(destination, user)
embedded = Mail.new(@embedded_email_raw)
email, display_name = parse_from_field(embedded)
embedded_user = find_or_create_user(email, display_name)
raw = embedded.decoded
title = embedded.subject.presence || subject
case destination[:type]
when :group
group = destination[:obj]
post = create_topic(user: embedded_user,
raw: raw,
title: title,
archetype: Archetype.private_message,
target_group_names: [group.name],
is_group_message: true,
skip_validations: true,
created_at: embedded.date)
when :category
category = destination[:obj]
return false if user.staged? && !category.email_in_allow_strangers
return false if !user.has_trust_level?(SiteSetting.email_in_min_trust)
post = create_topic(user: embedded_user,
raw: raw,
title: title,
category: category.id,
skip_validations: embedded_user.staged?,
created_at: embedded.date)
else
return false
end
if post && post.topic && @before_embedded.present?
create_reply(user: user,
raw: @before_embedded,
post: post,
topic: post.topic,
post_type: Post.types[:whisper])
end
true
end
def reply_by_email_address_regex def reply_by_email_address_regex
@reply_by_email_address_regex ||= begin @reply_by_email_address_regex ||= begin
reply_addresses = [ reply_addresses = [
@ -483,16 +550,17 @@ module Email
options[:raw_email] = @raw_email options[:raw_email] = @raw_email
# ensure posts aren't created in the future # ensure posts aren't created in the future
options[:created_at] = [@mail.date, DateTime.now].min options[:created_at] ||= @mail.date
options[:created_at] = DateTime.now if options[:created_at] > DateTime.now
is_private_message = options[:archetype] == Archetype.private_message || is_private_message = options[:archetype] == Archetype.private_message ||
options[:topic].try(:private_message?) options[:topic].try(:private_message?)
# only add elided part in messages # only add elided part in messages
if @elided.present? && is_private_message if options[:elided].present? && is_private_message
options[:raw] << "\n\n" << "<details class='elided'>" << "\n" options[:raw] << "\n\n" << "<details class='elided'>" << "\n"
options[:raw] << "<summary title='#{I18n.t('emails.incoming.show_trimmed_content')}'>&#183;&#183;&#183;</summary>" << "\n" options[:raw] << "<summary title='#{I18n.t('emails.incoming.show_trimmed_content')}'>&#183;&#183;&#183;</summary>" << "\n"
options[:raw] << @elided << "\n" options[:raw] << options[:elided] << "\n"
options[:raw] << "</details>" << "\n" options[:raw] << "</details>" << "\n"
end end
@ -508,6 +576,8 @@ module Email
add_other_addresses(result.post.topic, user) add_other_addresses(result.post.topic, user)
end end
end end
result.post
end end
def add_other_addresses(topic, sender) def add_other_addresses(topic, sender)
@ -518,6 +588,7 @@ module Email
address_field.decoded address_field.decoded
email = address_field.address.downcase email = address_field.address.downcase
display_name = address_field.display_name.try(:to_s) display_name = address_field.display_name.try(:to_s)
next unless email["@"]
if should_invite?(email) if should_invite?(email)
user = find_or_create_user(email, display_name) user = find_or_create_user(email, display_name)
if user && can_invite?(topic, user) if user && can_invite?(topic, user)