diff --git a/Gemfile b/Gemfile index 766c948480e..c91c6d77c24 100644 --- a/Gemfile +++ b/Gemfile @@ -66,7 +66,7 @@ gem 'aws-sdk', require: false gem 'excon', 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 # https://github.com/toy/image_optim diff --git a/Gemfile.lock b/Gemfile.lock index a823497bcc8..2a692f64489 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -80,7 +80,7 @@ GEM docile (1.1.5) domain_name (0.5.25) 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-source (~> 1.8) ember-handlebars-template (0.7.3) @@ -411,7 +411,7 @@ DEPENDENCIES certified discourse-qunit-rails discourse_fastimage (= 2.0.3) - email_reply_trimmer (= 0.1.4) + email_reply_trimmer (= 0.1.5) ember-rails (= 0.18.5) ember-source (= 1.12.2) excon diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index deadfea2682..adddf4aaf27 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1246,6 +1246,8 @@ en: 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." + 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." pop3_polling_enabled: "Poll via POP3 for email replies." pop3_polling_ssl: "Use SSL while connecting to the POP3 server. (Recommended)" diff --git a/config/site_settings.yml b/config/site_settings.yml index ea65c92cca8..26e4215d4b6 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -670,7 +670,7 @@ email: attachment_filename_blacklist: type: list default: "smime.p7s|signature.asc" - + enable_forwarded_emails: false files: max_image_size_kb: diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index 597bc0726e7..09be5327521 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -38,7 +38,7 @@ module Email def process! 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 process_internal rescue => e @@ -74,7 +74,7 @@ module Email raise InactiveUserError if !user.active && !user.staged raise BlockedUserError if user.blocked - body, @elided = select_body + body, elided = select_body body ||= "" raise NoBodyDetectedError if body.blank? && attachments.empty? @@ -90,6 +90,7 @@ module Email elsif post = find_related_post create_reply(user: user, raw: body, + elided: elided, post: post, topic: post.topic, skip_validations: user.staged?) @@ -98,7 +99,7 @@ module Email destinations.each do |destination| begin - process_destination(destination, user, body) + process_destination(destination, user, body, elided) rescue => e first_exception ||= e else @@ -237,16 +238,19 @@ module Email reply.split(previous_replies_regex)[0] end - def parse_from_field - if @mail[:from].errors.blank? - address_field = @mail[:from].address_list.addresses.first - address_field.decoded - from_address = address_field.address - from_display_name = address_field.display_name.try(:to_s) - else - from_address = @mail.from[/<([^>]+)>/, 1] - from_display_name = @mail.from[/^([^<]+)/, 1] + def parse_from_field(mail) + if mail[:from].errors.blank? + mail[:from].address_list.addresses.each do |address_field| + address_field.decoded + from_address = address_field.address + from_display_name = address_field.display_name.try(:to_s) + return [from_address.downcase, from_display_name] if from_address["@"] + end end + + from_address = mail.from[/<([^>]+)>/, 1] + from_display_name = mail.from[/^([^<]+)/, 1] + [from_address.downcase, from_display_name] end @@ -314,12 +318,17 @@ module Email 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] when :group group = destination[:obj] create_topic(user: user, raw: body, + elided: elided, title: subject, archetype: Archetype.private_message, target_group_names: [group.name], @@ -347,11 +356,69 @@ module Email create_reply(user: user, raw: body, + elided: elided, post: email_log.post, topic: email_log.post.topic) 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 @reply_by_email_address_regex ||= begin reply_addresses = [ @@ -483,16 +550,17 @@ module Email options[:raw_email] = @raw_email # 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 || options[:topic].try(:private_message?) # only add elided part in messages - if @elided.present? && is_private_message + if options[:elided].present? && is_private_message options[:raw] << "\n\n" << "
" << "\n" options[:raw] << "···" << "\n" - options[:raw] << @elided << "\n" + options[:raw] << options[:elided] << "\n" options[:raw] << "
" << "\n" end @@ -508,6 +576,8 @@ module Email add_other_addresses(result.post.topic, user) end end + + result.post end def add_other_addresses(topic, sender) @@ -518,6 +588,7 @@ module Email address_field.decoded email = address_field.address.downcase display_name = address_field.display_name.try(:to_s) + next unless email["@"] if should_invite?(email) user = find_or_create_user(email, display_name) if user && can_invite?(topic, user)