2016-03-14 13:18:58 -04:00
|
|
|
require "digest"
|
2016-01-18 18:57:55 -05:00
|
|
|
require_dependency "new_post_manager"
|
|
|
|
require_dependency "post_action_creator"
|
|
|
|
require_dependency "email/html_cleaner"
|
2013-06-10 16:46:08 -04:00
|
|
|
|
|
|
|
module Email
|
2014-04-14 16:55:57 -04:00
|
|
|
|
2013-06-10 16:46:08 -04:00
|
|
|
class Receiver
|
2016-01-29 19:29:31 -05:00
|
|
|
include ActionView::Helpers::NumberHelper
|
2013-06-10 16:46:08 -04:00
|
|
|
|
2016-04-18 16:58:30 -04:00
|
|
|
class ProcessingError < StandardError; end
|
|
|
|
class EmptyEmailError < ProcessingError; end
|
|
|
|
class ScreenedEmailError < ProcessingError; end
|
|
|
|
class UserNotFoundError < ProcessingError; end
|
|
|
|
class AutoGeneratedEmailError < ProcessingError; end
|
2016-04-07 10:21:17 -04:00
|
|
|
class AutoGeneratedEmailReplyError < ProcessingError; end
|
2016-04-18 16:58:30 -04:00
|
|
|
class BouncedEmailError < ProcessingError; end
|
|
|
|
class NoBodyDetectedError < ProcessingError; end
|
|
|
|
class InactiveUserError < ProcessingError; end
|
|
|
|
class BlockedUserError < ProcessingError; end
|
|
|
|
class BadDestinationAddress < ProcessingError; end
|
|
|
|
class StrangersNotAllowedError < ProcessingError; end
|
|
|
|
class InsufficientTrustLevelError < ProcessingError; end
|
|
|
|
class ReplyUserNotMatchingError < ProcessingError; end
|
|
|
|
class TopicNotFoundError < ProcessingError; end
|
|
|
|
class TopicClosedError < ProcessingError; end
|
|
|
|
class InvalidPost < ProcessingError; end
|
|
|
|
class InvalidPostAction < ProcessingError; end
|
2016-01-18 18:57:55 -05:00
|
|
|
|
2016-03-07 10:56:17 -05:00
|
|
|
attr_reader :incoming_email
|
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def initialize(mail_string)
|
|
|
|
raise EmptyEmailError if mail_string.blank?
|
2016-05-16 15:45:34 -04:00
|
|
|
@staged_users_created = 0
|
2016-01-18 18:57:55 -05:00
|
|
|
@raw_email = mail_string
|
|
|
|
@mail = Mail.new(@raw_email)
|
2016-03-14 13:18:58 -04:00
|
|
|
@message_id = @mail.message_id.presence || Digest::MD5.hexdigest(mail_string)
|
2013-06-10 16:46:08 -04:00
|
|
|
end
|
|
|
|
|
2016-03-07 10:56:17 -05:00
|
|
|
def process!
|
2016-05-18 17:07:01 -04:00
|
|
|
return if is_blacklisted?
|
2016-02-24 11:40:57 -05:00
|
|
|
@from_email, @from_display_name = parse_from_field
|
2016-01-18 18:57:55 -05:00
|
|
|
@incoming_email = find_or_create_incoming_email
|
|
|
|
process_internal
|
|
|
|
rescue => e
|
2016-05-18 17:07:01 -04:00
|
|
|
@incoming_email.update_columns(error: e.to_s) if @incoming_email
|
2016-01-18 18:57:55 -05:00
|
|
|
raise
|
|
|
|
end
|
2014-08-13 14:06:17 -04:00
|
|
|
|
2016-05-18 17:07:01 -04:00
|
|
|
def is_blacklisted?
|
|
|
|
return false if SiteSetting.ignore_by_title.blank?
|
|
|
|
Regexp.new(SiteSetting.ignore_by_title) =~ @mail.subject
|
|
|
|
end
|
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def find_or_create_incoming_email
|
2016-03-14 13:18:58 -04:00
|
|
|
IncomingEmail.find_or_create_by(message_id: @message_id) do |ie|
|
2016-03-07 10:56:17 -05:00
|
|
|
ie.raw = @raw_email
|
|
|
|
ie.subject = subject
|
|
|
|
ie.from_address = @from_email
|
|
|
|
ie.to_addresses = @mail.to.map(&:downcase).join(";") if @mail.to.present?
|
|
|
|
ie.cc_addresses = @mail.cc.map(&:downcase).join(";") if @mail.cc.present?
|
2016-01-18 18:57:55 -05:00
|
|
|
end
|
|
|
|
end
|
2014-02-27 10:36:33 -05:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def process_internal
|
2016-04-18 16:58:30 -04:00
|
|
|
raise ScreenedEmailError if ScreenedEmail.should_block?(@from_email)
|
|
|
|
|
2016-02-24 11:40:57 -05:00
|
|
|
user = find_or_create_user(@from_email, @from_display_name)
|
2016-03-23 13:56:03 -04:00
|
|
|
|
|
|
|
raise UserNotFoundError if user.nil?
|
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
@incoming_email.update_columns(user_id: user.id)
|
2016-03-09 12:51:54 -05:00
|
|
|
|
2016-05-02 17:15:32 -04:00
|
|
|
raise BouncedEmailError if is_bounce?
|
2016-04-20 15:29:27 -04:00
|
|
|
raise InactiveUserError if !user.active && !user.staged
|
|
|
|
raise BlockedUserError if user.blocked
|
|
|
|
|
2016-03-17 18:10:46 -04:00
|
|
|
body, @elided = select_body
|
2016-03-09 12:51:54 -05:00
|
|
|
body ||= ""
|
2016-03-11 11:51:16 -05:00
|
|
|
|
2016-04-20 15:29:27 -04:00
|
|
|
raise NoBodyDetectedError if body.blank? && !@mail.has_attachments?
|
|
|
|
|
|
|
|
if is_auto_generated?
|
|
|
|
@incoming_email.update_columns(is_auto_generated: true)
|
|
|
|
raise AutoGeneratedEmailReplyError if check_reply_to_auto_generated_header
|
|
|
|
raise AutoGeneratedEmailError if SiteSetting.block_auto_generated_emails?
|
|
|
|
end
|
2015-12-07 11:01:08 -05:00
|
|
|
|
2016-02-01 06:16:15 -05:00
|
|
|
if action = subscription_action_for(body, subject)
|
2016-01-20 04:25:25 -05:00
|
|
|
message = SubscriptionMailer.send(action, user)
|
|
|
|
Email::Sender.new(message, :subscription).send
|
|
|
|
elsif post = find_related_post
|
2016-03-14 17:21:18 -04:00
|
|
|
create_reply(user: user,
|
|
|
|
raw: body,
|
|
|
|
post: post,
|
|
|
|
topic: post.topic,
|
|
|
|
skip_validations: user.staged?)
|
2016-01-18 18:57:55 -05:00
|
|
|
else
|
|
|
|
destination = destinations.first
|
2015-12-07 11:01:08 -05:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
raise BadDestinationAddress if destination.blank?
|
2014-08-13 14:06:17 -04:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
case destination[:type]
|
|
|
|
when :group
|
|
|
|
group = destination[:obj]
|
2016-02-29 16:39:24 -05:00
|
|
|
create_topic(user: user,
|
|
|
|
raw: body,
|
|
|
|
title: subject,
|
|
|
|
archetype: Archetype.private_message,
|
|
|
|
target_group_names: [group.name],
|
|
|
|
is_group_message: true,
|
|
|
|
skip_validations: true)
|
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
when :category
|
|
|
|
category = destination[:obj]
|
2014-08-13 14:06:17 -04:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
raise StrangersNotAllowedError if user.staged? && !category.email_in_allow_strangers
|
|
|
|
raise InsufficientTrustLevelError if !user.has_trust_level?(SiteSetting.email_in_min_trust)
|
2014-08-13 14:06:17 -04:00
|
|
|
|
2016-02-29 16:39:24 -05:00
|
|
|
create_topic(user: user,
|
|
|
|
raw: body,
|
|
|
|
title: subject,
|
|
|
|
category: category.id,
|
|
|
|
skip_validations: user.staged?)
|
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
when :reply
|
|
|
|
email_log = destination[:obj]
|
2013-06-13 18:11:10 -04:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
raise ReplyUserNotMatchingError if email_log.user_id != user.id
|
2013-06-20 12:38:03 -04:00
|
|
|
|
2016-02-29 16:39:24 -05:00
|
|
|
create_reply(user: user,
|
|
|
|
raw: body,
|
|
|
|
post: email_log.post,
|
|
|
|
topic: email_log.post.topic)
|
2016-01-18 18:57:55 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2014-08-26 20:31:51 -04:00
|
|
|
|
2016-05-02 17:15:32 -04:00
|
|
|
SOFT_BOUNCE_SCORE ||= 1
|
|
|
|
HARD_BOUNCE_SCORE ||= 2
|
|
|
|
|
|
|
|
def is_bounce?
|
|
|
|
return false unless @mail.bounced? || verp
|
|
|
|
|
|
|
|
@incoming_email.update_columns(is_bounce: true)
|
|
|
|
|
|
|
|
if verp
|
|
|
|
bounce_key = verp[/\+verp-(\h{32})@/, 1]
|
|
|
|
if bounce_key && (email_log = EmailLog.find_by(bounce_key: bounce_key))
|
|
|
|
email_log.update_columns(bounced: true)
|
|
|
|
|
|
|
|
if @mail.error_status.present?
|
|
|
|
if @mail.error_status.start_with?("4.")
|
2016-05-30 11:11:17 -04:00
|
|
|
Email::Receiver.update_bounce_score(email_log.user.email, SOFT_BOUNCE_SCORE)
|
2016-05-02 17:15:32 -04:00
|
|
|
elsif @mail.error_status.start_with?("5.")
|
2016-05-30 11:11:17 -04:00
|
|
|
Email::Receiver.update_bounce_score(email_log.user.email, HARD_BOUNCE_SCORE)
|
2016-05-02 17:15:32 -04:00
|
|
|
end
|
2016-05-16 03:01:15 -04:00
|
|
|
elsif is_auto_generated?
|
2016-05-30 11:11:17 -04:00
|
|
|
Email::Receiver.update_bounce_score(email_log.user.email, HARD_BOUNCE_SCORE)
|
2016-05-02 17:15:32 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
def verp
|
2016-05-06 13:34:33 -04:00
|
|
|
@verp ||= all_destinations.select { |to| to[/\+verp-\h{32}@/] }.first
|
2016-05-02 17:15:32 -04:00
|
|
|
end
|
|
|
|
|
2016-05-30 11:11:17 -04:00
|
|
|
def self.update_bounce_score(email, score)
|
2016-05-02 17:15:32 -04:00
|
|
|
# only update bounce score once per day
|
|
|
|
key = "bounce_score:#{email}:#{Date.today}"
|
|
|
|
|
|
|
|
if $redis.setnx(key, "1")
|
|
|
|
$redis.expire(key, 25.hours)
|
|
|
|
|
|
|
|
if user = User.find_by(email: email)
|
|
|
|
user.user_stat.bounce_score += score
|
|
|
|
user.user_stat.reset_bounce_score_after = 30.days.from_now
|
|
|
|
user.user_stat.save
|
|
|
|
|
2016-05-06 13:34:33 -04:00
|
|
|
if user.user_stat.bounce_score >= SiteSetting.bounce_score_threshold
|
2016-05-02 17:15:32 -04:00
|
|
|
StaffActionLogger.new(Discourse.system_user).log_revoke_email(user)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
true
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def is_auto_generated?
|
2016-04-11 16:47:34 -04:00
|
|
|
return false if SiteSetting.auto_generated_whitelist.split('|').include?(@from_email)
|
2016-03-30 12:41:09 -04:00
|
|
|
@mail[:precedence].to_s[/list|junk|bulk|auto_reply/i] ||
|
|
|
|
@mail[:from].to_s[/(mailer-?daemon|postmaster|noreply)@/i] ||
|
|
|
|
@mail.header.to_s[/auto[\-_]?(response|submitted|replied|reply|generated|respond)|holidayreply|machinegenerated/i]
|
2014-08-26 20:31:51 -04:00
|
|
|
end
|
2013-06-19 12:14:01 -04:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def select_body
|
|
|
|
text = nil
|
2014-08-26 20:31:51 -04:00
|
|
|
html = nil
|
2015-11-30 12:33:24 -05:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
if @mail.multipart?
|
|
|
|
text = fix_charset(@mail.text_part)
|
|
|
|
html = fix_charset(@mail.html_part)
|
|
|
|
elsif @mail.content_type.to_s["text/html"]
|
|
|
|
html = fix_charset(@mail)
|
|
|
|
else
|
|
|
|
text = fix_charset(@mail)
|
2014-01-16 21:24:32 -05:00
|
|
|
end
|
2014-03-28 09:57:12 -04:00
|
|
|
|
2016-06-06 04:30:04 -04:00
|
|
|
if html.present? && (SiteSetting.incoming_email_prefer_html || text.blank?)
|
2016-05-26 04:13:01 -04:00
|
|
|
html = Email::HtmlCleaner.new(html).output_html
|
|
|
|
html = trim_discourse_markers(html)
|
|
|
|
html, elided = EmailReplyTrimmer.trim(html, true)
|
|
|
|
return [html, elided]
|
|
|
|
end
|
2016-06-06 04:30:04 -04:00
|
|
|
|
|
|
|
if text.present?
|
|
|
|
text = trim_discourse_markers(text)
|
|
|
|
text, elided = EmailReplyTrimmer.trim(text, true)
|
|
|
|
return [text, elided]
|
|
|
|
end
|
2016-01-18 18:57:55 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def fix_charset(mail_part)
|
|
|
|
return nil if mail_part.blank? || mail_part.body.blank?
|
|
|
|
|
2016-01-29 19:29:31 -05:00
|
|
|
string = mail_part.body.decoded rescue nil
|
2013-06-20 12:38:03 -04:00
|
|
|
|
2016-01-29 19:29:31 -05:00
|
|
|
return nil if string.blank?
|
2015-05-22 15:40:26 -04:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
# 1) use the charset provided
|
|
|
|
if mail_part.charset.present?
|
|
|
|
fixed = try_to_encode(string, mail_part.charset)
|
|
|
|
return fixed if fixed.present?
|
2014-08-26 20:31:51 -04:00
|
|
|
end
|
2013-11-20 13:29:42 -05:00
|
|
|
|
2016-03-30 13:54:38 -04:00
|
|
|
# 2) try most used encodings
|
|
|
|
try_to_encode(string, "UTF-8") || try_to_encode(string, "ISO-8859-1")
|
2016-01-18 18:57:55 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def try_to_encode(string, encoding)
|
2016-03-30 13:54:38 -04:00
|
|
|
encoded = string.encode("UTF-8", encoding)
|
|
|
|
encoded.present? && encoded.valid_encoding? ? encoded : nil
|
2016-03-11 12:51:53 -05:00
|
|
|
rescue Encoding::InvalidByteSequenceError,
|
|
|
|
Encoding::UndefinedConversionError,
|
|
|
|
Encoding::ConverterNotFoundError
|
2016-01-18 18:57:55 -05:00
|
|
|
nil
|
2013-06-20 12:38:03 -04:00
|
|
|
end
|
|
|
|
|
2016-01-29 19:29:31 -05:00
|
|
|
def previous_replies_regex
|
2016-02-11 12:48:09 -05:00
|
|
|
@previous_replies_regex ||= /^--[- ]\n\*#{I18n.t("user_notifications.previous_discussion")}\*\n/im
|
2016-01-29 19:29:31 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def trim_discourse_markers(reply)
|
|
|
|
reply.split(previous_replies_regex)[0]
|
|
|
|
end
|
|
|
|
|
2016-02-24 11:40:57 -05:00
|
|
|
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]
|
|
|
|
end
|
|
|
|
[from_address.downcase, from_display_name]
|
2016-01-18 18:57:55 -05:00
|
|
|
end
|
2016-02-01 06:16:15 -05:00
|
|
|
|
|
|
|
def subject
|
2016-02-24 11:40:57 -05:00
|
|
|
@suject ||= @mail.subject.presence || I18n.t("emails.incoming.default_subject", email: @from_email)
|
2016-02-01 06:16:15 -05:00
|
|
|
end
|
2013-06-20 12:38:03 -04:00
|
|
|
|
2016-02-24 11:40:57 -05:00
|
|
|
def find_or_create_user(email, display_name)
|
2016-03-23 13:56:03 -04:00
|
|
|
user = nil
|
|
|
|
|
|
|
|
User.transaction do
|
2016-04-18 16:58:30 -04:00
|
|
|
begin
|
|
|
|
user = User.find_by_email(email)
|
|
|
|
|
|
|
|
if user.nil? && SiteSetting.enable_staged_users
|
|
|
|
username = UserNameSuggester.sanitize_username(display_name) if display_name.present?
|
|
|
|
user = User.create!(
|
|
|
|
email: email,
|
|
|
|
username: UserNameSuggester.suggest(username.presence || email),
|
|
|
|
name: display_name.presence || User.suggest_name(email),
|
|
|
|
staged: true
|
|
|
|
)
|
2016-05-16 15:45:34 -04:00
|
|
|
@staged_users_created += 1
|
2016-04-18 16:58:30 -04:00
|
|
|
end
|
|
|
|
rescue
|
|
|
|
user = nil
|
2016-03-23 13:56:03 -04:00
|
|
|
end
|
2014-08-26 20:31:51 -04:00
|
|
|
end
|
2016-03-23 13:56:03 -04:00
|
|
|
|
|
|
|
user
|
2013-06-19 12:14:01 -04:00
|
|
|
end
|
2013-06-13 18:11:10 -04:00
|
|
|
|
2016-05-06 13:34:33 -04:00
|
|
|
def all_destinations
|
|
|
|
@all_destinations ||= [
|
|
|
|
@mail.destinations,
|
2016-01-18 18:57:55 -05:00
|
|
|
[@mail[:x_forwarded_to]].flatten.compact.map(&:decoded),
|
|
|
|
[@mail[:delivered_to]].flatten.compact.map(&:decoded),
|
2016-05-06 13:34:33 -04:00
|
|
|
].flatten.select(&:present?).uniq.lazy
|
|
|
|
end
|
|
|
|
|
|
|
|
def destinations
|
|
|
|
all_destinations
|
|
|
|
.map { |d| check_address(d) }
|
|
|
|
.drop_while(&:blank?)
|
2015-11-18 15:22:50 -05:00
|
|
|
end
|
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def check_address(address)
|
|
|
|
# only check for a group/category when 'email_in' is enabled
|
|
|
|
if SiteSetting.email_in
|
|
|
|
group = Group.find_by_email(address)
|
|
|
|
return { type: :group, obj: group } if group
|
2013-07-24 14:22:32 -04:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
category = Category.find_by_email(address)
|
|
|
|
return { type: :category, obj: category } if category
|
2015-11-18 15:22:50 -05:00
|
|
|
end
|
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
# reply
|
|
|
|
match = reply_by_email_address_regex.match(address)
|
|
|
|
if match && match[1].present?
|
|
|
|
email_log = EmailLog.for(match[1])
|
|
|
|
return { type: :reply, obj: email_log } if email_log
|
2013-07-24 14:22:32 -04:00
|
|
|
end
|
2016-01-18 18:57:55 -05:00
|
|
|
end
|
2013-07-24 14:22:32 -04:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def reply_by_email_address_regex
|
|
|
|
@reply_by_email_address_regex ||= Regexp.new Regexp.escape(SiteSetting.reply_by_email_address)
|
|
|
|
.gsub(Regexp.escape("%{reply_key}"), "([[:xdigit:]]{32})")
|
2013-07-24 14:22:32 -04:00
|
|
|
end
|
|
|
|
|
2016-01-20 17:08:27 -05:00
|
|
|
def group_incoming_emails_regex
|
2016-02-24 13:47:58 -05:00
|
|
|
@group_incoming_emails_regex ||= Regexp.union Group.pluck(:incoming_email).select(&:present?).map { |e| e.split("|") }.flatten.uniq
|
2016-01-20 17:08:27 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def category_email_in_regex
|
2016-02-24 13:47:58 -05:00
|
|
|
@category_email_in_regex ||= Regexp.union Category.pluck(:email_in).select(&:present?).map { |e| e.split("|") }.flatten.uniq
|
2016-01-20 17:08:27 -05:00
|
|
|
end
|
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def find_related_post
|
2016-02-10 16:00:27 -05:00
|
|
|
message_ids = [@mail.in_reply_to, Email::Receiver.extract_references(@mail.references)]
|
2016-01-18 18:57:55 -05:00
|
|
|
message_ids.flatten!
|
|
|
|
message_ids.select!(&:present?)
|
|
|
|
message_ids.uniq!
|
|
|
|
return if message_ids.empty?
|
|
|
|
|
2016-01-20 16:52:08 -05:00
|
|
|
Post.where(id: IncomingEmail.where(message_id: message_ids).select(:post_id))
|
|
|
|
.order(created_at: :desc)
|
|
|
|
.first
|
2016-01-18 18:57:55 -05:00
|
|
|
end
|
2015-11-24 10:58:26 -05:00
|
|
|
|
2016-02-10 16:00:27 -05:00
|
|
|
def self.extract_references(references)
|
|
|
|
if Array === references
|
|
|
|
references
|
|
|
|
elsif references.present?
|
|
|
|
references.split(/[\s,]/).map { |r| r.sub(/^</, "").sub(/>$/, "") }
|
2016-01-18 18:57:55 -05:00
|
|
|
end
|
2014-04-14 16:55:57 -04:00
|
|
|
end
|
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def likes
|
|
|
|
@likes ||= Set.new ["+1", I18n.t('post_action_types.like.title').downcase]
|
2015-12-30 06:17:45 -05:00
|
|
|
end
|
|
|
|
|
2016-01-20 04:25:25 -05:00
|
|
|
def subscription_action_for(body, subject)
|
|
|
|
return unless SiteSetting.unsubscribe_via_email
|
|
|
|
if ([subject, body].compact.map(&:to_s).map(&:downcase) & ['unsubscribe']).any?
|
|
|
|
:confirm_unsubscribe
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-12-30 06:17:45 -05:00
|
|
|
def post_action_for(body)
|
2016-01-18 18:57:55 -05:00
|
|
|
if likes.include?(body.strip.downcase)
|
2015-12-30 06:17:45 -05:00
|
|
|
PostActionType.types[:like]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def create_topic(options={})
|
|
|
|
create_post_with_attachments(options)
|
2013-06-10 16:46:08 -04:00
|
|
|
end
|
2014-02-24 11:36:53 -05:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def create_reply(options={})
|
|
|
|
raise TopicNotFoundError if options[:topic].nil? || options[:topic].trashed?
|
|
|
|
raise TopicClosedError if options[:topic].closed?
|
2014-04-14 16:55:57 -04:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
if post_action_type = post_action_for(options[:raw])
|
|
|
|
create_post_action(options[:user], options[:post], post_action_type)
|
|
|
|
else
|
|
|
|
options[:topic_id] = options[:post].try(:topic_id)
|
|
|
|
options[:reply_to_post_number] = options[:post].try(:post_number)
|
2016-02-29 16:39:24 -05:00
|
|
|
options[:is_group_message] = options[:topic].private_message? && options[:topic].allowed_groups.exists?
|
2016-01-18 18:57:55 -05:00
|
|
|
create_post_with_attachments(options)
|
|
|
|
end
|
2014-04-14 16:55:57 -04:00
|
|
|
end
|
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def create_post_action(user, post, type)
|
|
|
|
PostActionCreator.new(user, post).perform(type)
|
|
|
|
rescue PostAction::AlreadyActed
|
|
|
|
# it's cool, don't care
|
|
|
|
rescue Discourse::InvalidAccess => e
|
|
|
|
raise InvalidPostAction.new(e)
|
|
|
|
end
|
2014-04-14 16:55:57 -04:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def create_post_with_attachments(options={})
|
2014-04-14 16:55:57 -04:00
|
|
|
# deal with attachments
|
2016-01-18 18:57:55 -05:00
|
|
|
@mail.attachments.each do |attachment|
|
2014-04-14 16:55:57 -04:00
|
|
|
tmp = Tempfile.new("discourse-email-attachment")
|
|
|
|
begin
|
|
|
|
# read attachment
|
|
|
|
File.open(tmp.path, "w+b") { |f| f.write attachment.body.decoded }
|
|
|
|
# create the upload for the user
|
2016-02-29 16:39:24 -05:00
|
|
|
opts = { is_attachment_for_group_message: options[:is_group_message] }
|
|
|
|
upload = Upload.create_for(options[:user].id, tmp, attachment.filename, tmp.size, opts)
|
2014-04-14 16:55:57 -04:00
|
|
|
if upload && upload.errors.empty?
|
2015-11-30 12:33:24 -05:00
|
|
|
# try to inline images
|
2016-01-18 18:57:55 -05:00
|
|
|
if attachment.content_type.start_with?("image/") && options[:raw][/\[image: .+ \d+\]/]
|
|
|
|
options[:raw].sub!(/\[image: .+ \d+\]/, attachment_markdown(upload))
|
|
|
|
else
|
2016-02-24 05:38:12 -05:00
|
|
|
options[:raw] << "\n\n#{attachment_markdown(upload)}\n\n"
|
2015-11-30 12:33:24 -05:00
|
|
|
end
|
2014-04-14 16:55:57 -04:00
|
|
|
end
|
|
|
|
ensure
|
2016-01-18 18:57:55 -05:00
|
|
|
tmp.try(:close!) rescue nil
|
2014-04-14 16:55:57 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-02-24 09:16:29 -05:00
|
|
|
create_post(options)
|
2014-04-14 16:55:57 -04:00
|
|
|
end
|
|
|
|
|
2014-04-14 18:04:13 -04:00
|
|
|
def attachment_markdown(upload)
|
|
|
|
if FileHelper.is_image?(upload.original_filename)
|
2014-04-14 16:55:57 -04:00
|
|
|
"<img src='#{upload.url}' width='#{upload.width}' height='#{upload.height}'>"
|
|
|
|
else
|
|
|
|
"<a class='attachment' href='#{upload.url}'>#{upload.original_filename}</a> (#{number_to_human_size(upload.filesize)})"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def create_post(options={})
|
2014-09-04 13:04:22 -04:00
|
|
|
options[:via_email] = true
|
2016-01-18 18:57:55 -05:00
|
|
|
options[:raw_email] = @raw_email
|
2014-09-04 13:04:22 -04:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
# ensure posts aren't created in the future
|
|
|
|
options[:created_at] = [@mail.date, DateTime.now].min
|
|
|
|
|
2016-06-06 04:30:04 -04:00
|
|
|
is_private_message = options[:archetype] == Archetype.private_message ||
|
|
|
|
options[:topic].try(:private_message?)
|
|
|
|
|
2016-03-17 18:10:46 -04:00
|
|
|
# only add elided part in messages
|
2016-06-06 04:30:04 -04:00
|
|
|
if @elided.present? && is_private_message
|
2016-03-17 18:10:46 -04:00
|
|
|
options[:raw] << "\n\n" << "<details class='elided'>" << "\n"
|
|
|
|
options[:raw] << "<summary title='#{I18n.t('emails.incoming.show_trimmed_content')}'>···</summary>" << "\n"
|
|
|
|
options[:raw] << @elided << "\n"
|
|
|
|
options[:raw] << "</details>" << "\n"
|
|
|
|
end
|
|
|
|
|
2016-04-11 12:20:26 -04:00
|
|
|
user = options.delete(:user)
|
|
|
|
manager = NewPostManager.new(user, options)
|
2015-04-28 13:04:34 -04:00
|
|
|
result = manager.perform
|
2014-08-26 20:30:12 -04:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
raise InvalidPost, result.errors.full_messages.join("\n") if result.errors.any?
|
|
|
|
|
|
|
|
if result.post
|
|
|
|
@incoming_email.update_columns(topic_id: result.post.topic_id, post_id: result.post.id)
|
|
|
|
if result.post.topic && result.post.topic.private_message?
|
2016-04-11 12:20:26 -04:00
|
|
|
add_other_addresses(result.post.topic, user)
|
2016-01-18 18:57:55 -05:00
|
|
|
end
|
2014-07-31 04:46:02 -04:00
|
|
|
end
|
2016-01-18 18:57:55 -05:00
|
|
|
end
|
2014-08-26 20:30:12 -04:00
|
|
|
|
2016-01-18 18:57:55 -05:00
|
|
|
def add_other_addresses(topic, sender)
|
|
|
|
%i(to cc bcc).each do |d|
|
|
|
|
if @mail[d] && @mail[d].address_list && @mail[d].address_list.addresses
|
2016-01-19 09:24:34 -05:00
|
|
|
@mail[d].address_list.addresses.each do |address_field|
|
2016-01-18 18:57:55 -05:00
|
|
|
begin
|
2016-02-24 11:40:57 -05:00
|
|
|
address_field.decoded
|
2016-01-19 09:24:34 -05:00
|
|
|
email = address_field.address.downcase
|
2016-02-24 11:40:57 -05:00
|
|
|
display_name = address_field.display_name.try(:to_s)
|
2016-01-20 17:08:27 -05:00
|
|
|
if should_invite?(email)
|
2016-02-24 11:40:57 -05:00
|
|
|
user = find_or_create_user(email, display_name)
|
2016-03-23 13:56:03 -04:00
|
|
|
if user && can_invite?(topic, user)
|
2016-01-18 18:57:55 -05:00
|
|
|
topic.topic_allowed_users.create!(user_id: user.id)
|
|
|
|
topic.add_small_action(sender, "invited_user", user.username)
|
|
|
|
end
|
2016-05-16 15:45:34 -04:00
|
|
|
# cap number of staged users created per email
|
|
|
|
if @staged_users_created > SiteSetting.maximum_staged_users_per_email
|
|
|
|
topic.add_moderator_post(sender, I18n.t("emails.incoming.maximum_staged_user_per_email_reached"))
|
|
|
|
return
|
|
|
|
end
|
2016-01-18 18:57:55 -05:00
|
|
|
end
|
|
|
|
rescue ActiveRecord::RecordInvalid
|
|
|
|
# don't care if user already allowed
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2014-02-24 01:01:37 -05:00
|
|
|
end
|
2013-06-10 16:46:08 -04:00
|
|
|
|
2016-01-20 17:08:27 -05:00
|
|
|
def should_invite?(email)
|
|
|
|
email !~ reply_by_email_address_regex &&
|
|
|
|
email !~ group_incoming_emails_regex &&
|
|
|
|
email !~ category_email_in_regex
|
|
|
|
end
|
|
|
|
|
2016-01-19 09:24:34 -05:00
|
|
|
def can_invite?(topic, user)
|
|
|
|
!topic.topic_allowed_users.where(user_id: user.id).exists? &&
|
|
|
|
!topic.topic_allowed_groups.where("group_id IN (SELECT group_id FROM group_users WHERE user_id = ?)", user.id).exists?
|
|
|
|
end
|
|
|
|
|
2016-04-07 10:21:17 -04:00
|
|
|
private
|
|
|
|
|
|
|
|
def check_reply_to_auto_generated_header
|
|
|
|
headers = Mail::Header.new(@mail.body.to_s.gsub("\r\n\r\n", "\r\n")).to_a
|
|
|
|
|
|
|
|
index = headers.find_index do |f|
|
|
|
|
f.name == Email::MessageBuilder::REPLY_TO_AUTO_GENERATED_HEADER_KEY
|
|
|
|
end
|
|
|
|
|
|
|
|
if index
|
|
|
|
headers[index].value == Email::MessageBuilder::REPLY_TO_AUTO_GENERATED_HEADER_VALUE
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-06-10 16:46:08 -04:00
|
|
|
end
|
2016-01-18 18:57:55 -05:00
|
|
|
|
2013-06-10 16:46:08 -04:00
|
|
|
end
|