Files
danbooru/app/models/dmail.rb
evazion b48211cd4a dmails: only spam check recipient's copy of the dmail.
Each dmail creates two copies, one for the sender and one for the
receiver. Only spam check the receiver's copy.

Prevents senders from being able to tell when their messages are being
spam filtered.
2017-12-15 18:54:18 -06:00

265 lines
6.1 KiB
Ruby

require 'digest/sha1'
class Dmail < ApplicationRecord
include Rakismet::Model
with_options on: :create do
validates_presence_of :to_id
validates_presence_of :from_id
validates_format_of :title, :with => /\S/
validates_format_of :body, :with => /\S/
validate :validate_sender_is_not_banned
end
belongs_to :owner, :class_name => "User"
belongs_to :to, :class_name => "User"
belongs_to :from, :class_name => "User"
after_initialize :initialize_attributes, if: :new_record?
before_create :auto_read_if_filtered
after_create :update_recipient
after_create :send_dmail
rakismet_attrs author: :from_name, author_email: :from_email, content: :title_and_body, user_ip: :creator_ip_addr_str
concerning :SpamMethods do
def title_and_body
"#{title}\n\n#{body}"
end
def creator_ip_addr_str
creator_ip_addr.to_s
end
def spam?
return false if Danbooru.config.rakismet_key.blank?
return false if from.is_gold?
super()
end
end
module AddressMethods
def to_name
User.id_to_pretty_name(to_id)
end
def from_name
User.id_to_pretty_name(from_id)
end
def from_email
from.email
end
def to_name=(name)
self.to_id = User.name_to_id(name)
end
def initialize_attributes
self.from_id ||= CurrentUser.id
self.creator_ip_addr ||= CurrentUser.ip_addr
end
end
module FactoryMethods
extend ActiveSupport::Concern
module ClassMethods
def create_split(params)
copy = nil
Dmail.transaction do
# recipient's copy
copy = Dmail.new(params)
copy.owner_id = copy.to_id
copy.is_spam = copy.spam?
copy.save unless copy.to_id == copy.from_id
# sender's copy
copy = Dmail.new(params)
copy.owner_id = copy.from_id
copy.is_read = true
copy.save
end
copy
end
def create_automated(params)
dmail = Dmail.new(from: Danbooru.config.system_user, **params)
dmail.owner = dmail.to
dmail.save
dmail
end
end
def build_response(options = {})
Dmail.new do |dmail|
if title =~ /Re:/
dmail.title = title
else
dmail.title = "Re: #{title}"
end
dmail.owner_id = from_id
dmail.body = quoted_body
dmail.to_id = from_id unless options[:forward]
dmail.from_id = to_id
end
end
end
module ApiMethods
def hidden_attributes
super + [:message_index]
end
def method_attributes
super + [:key]
end
end
module SearchMethods
def active
where("is_deleted = ?", false)
end
def deleted
where("is_deleted = ?", true)
end
def title_matches(query)
query = "*#{query}*" unless query =~ /\*/
where("lower(dmails.title) LIKE ?", query.mb_chars.downcase.to_escaped_for_sql_like)
end
def search_message(query)
if query =~ /\*/ && CurrentUser.user.is_builder?
escaped_query = query.to_escaped_for_sql_like
where("(title ILIKE ? ESCAPE E'\\\\' OR body ILIKE ? ESCAPE E'\\\\')", escaped_query, escaped_query)
else
where("message_index @@ plainto_tsquery(?)", query.to_escaped_for_tsquery_split)
end
end
def unread
where("is_read = false and is_deleted = false")
end
def visible
where("owner_id = ?", CurrentUser.id)
end
def to_name_matches(name)
where("to_id = (select _.id from users _ where lower(_.name) = ?)", name.mb_chars.downcase)
end
def from_name_matches(name)
where("from_id = (select _.id from users _ where lower(_.name) = ?)", name.mb_chars.downcase)
end
def search(params)
q = where("true")
return q if params.blank?
if params[:title_matches].present?
q = q.title_matches(params[:title_matches])
end
if params[:message_matches].present?
q = q.search_message(params[:message_matches])
end
if params[:to_name].present?
q = q.to_name_matches(params[:to_name])
end
if params[:to_id].present?
q = q.where("to_id = ?", params[:to_id].to_i)
end
if params[:from_name].present?
q = q.from_name_matches(params[:from_name])
end
if params[:from_id].present?
q = q.where("from_id = ?", params[:from_id].to_i)
end
if params[:is_spam].present?
q = q.where("is_spam = ?", true)
else
q = q.where("is_spam = ?", false)
end
if params[:read] == "true"
q = q.where("is_read = true")
elsif params[:read] == "false"
q = q.unread
end
q
end
end
include AddressMethods
include FactoryMethods
include ApiMethods
extend SearchMethods
def validate_sender_is_not_banned
if from.is_banned?
errors[:base] = "Sender is banned and cannot send messages"
return false
else
return true
end
end
def quoted_body
"[quote]\n#{from_name} said:\n\n#{body}\n[/quote]\n\n"
end
def send_dmail
if !is_spam? && to.receive_email_notifications? && to.email =~ /@/ && owner_id == to.id
UserMailer.dmail_notice(self).deliver_now
end
end
def mark_as_read!
update_column(:is_read, true)
unless Dmail.where(:is_read => false, :owner_id => CurrentUser.user.id).exists?
CurrentUser.user.update_attribute(:has_mail, false)
end
end
def is_automated?
from == Danbooru.config.system_user
end
def filtered?
CurrentUser.dmail_filter.try(:filtered?, self)
end
def auto_read_if_filtered
if owner_id != CurrentUser.id && to.dmail_filter.try(:filtered?, self)
self.is_read = true
end
end
def update_recipient
if owner_id != CurrentUser.user.id && !is_deleted? && !is_read?
to.update_attribute(:has_mail, true)
end
end
def key
digest = OpenSSL::Digest.new("sha256")
OpenSSL::HMAC.hexdigest(digest, Danbooru.config.email_key, "#{title} #{body}")
end
def visible_to?(user, key)
owner_id == user.id || (user.is_moderator? && key == self.key)
end
end