Files
danbooru/app/models/moderation_report.rb
evazion d9dc84325f Fix #5365: Don't allow whitespace-only text submission.
Fix bug where it was possible to submit blank text in various text fields.

Caused by `String#blank?` not considering certain Unicode characters as blank. `blank?` is defined
as `match?(/\A[[:space:]]*\z/)`, where `[[:space:]]` matches ASCII spaces (space, tab, newline, etc)
and Unicode characters in the Space category ([1]). However, there are other space-like characters
not in the Space category. This includes U+200B (Zero-Width Space), and many more.

It turns out the "Default ignorable code points" [2][3] are what we're after. These are the set of 400
or so formatting and control characters that are invisible when displayed.

Note that there are other control characters that aren't invisible when rendered, instead they're
shown with a placeholder glyph. These include the ASCII C0 and C1 control codes [4], certain Unicode
control characters [5], and unassigned, reserved, and private use codepoints.

There is one outlier: the Braille pattern blank (U+2800) [6]. This character is visually blank, but is
not considered to be a space or an ignorable code point.

[1]: https://codepoints.net/search?gc[]=Z
[2]: https://codepoints.net/search?DI=1
[3]: https://www.unicode.org/review/pr-5.html
[4]: https://codepoints.net/search?gc[]=Cc
[5]: https://codepoints.net/search?gc[]=Cf
[6]: https://codepoints.net/U+2800
[7]: https://en.wikipedia.org/wiki/Whitespace_character
[8]: https://character.construction/blanks
[9]: https://invisible-characters.com
2022-12-05 01:58:34 -06:00

102 lines
2.8 KiB
Ruby

# frozen_string_literal: true
class ModerationReport < ApplicationRecord
MODEL_TYPES = %w[Dmail Comment ForumPost]
attr_accessor :updater
belongs_to :model, polymorphic: true
belongs_to :creator, class_name: "User"
has_many :mod_actions, as: :subject, dependent: :destroy
before_validation(on: :create) { model.lock! }
validates :reason, visible_string: true
validates :model_type, inclusion: { in: MODEL_TYPES }
validates :creator, uniqueness: { scope: [:model_type, :model_id], message: "have already reported this message." }, on: :create
after_create :autoban_reported_user
after_save :notify_reporter
after_save :create_modaction
scope :dmail, -> { where(model_type: "Dmail") }
scope :comment, -> { where(model_type: "Comment") }
scope :forum_post, -> { where(model_type: "ForumPost") }
scope :recent, -> { where("moderation_reports.created_at >= ?", 1.week.ago) }
enum status: {
pending: 0,
rejected: 1,
handled: 2,
}
def self.model_types
MODEL_TYPES
end
def self.visible(user)
if user.is_moderator?
all
elsif !user.is_anonymous?
where(creator: user)
else
none
end
end
def autoban_reported_user
if SpamDetector.is_spammer?(reported_user)
SpamDetector.ban_spammer!(reported_user)
end
end
def notify_reporter
return if creator == User.system
return unless handled? && status_before_last_save != :handled
Dmail.create_automated(to: creator, title: "Thank you for reporting #{model.dtext_shortlink}", body: <<~EOS)
Thank you for reporting #{model.dtext_shortlink}. Action has been taken against the user.
EOS
end
def create_modaction
return unless saved_change_to_status? && status != :pending
if handled?
ModAction.log("handled modreport ##{id}", :moderation_report_handled, subject: self, user: updater)
elsif rejected?
ModAction.log("rejected modreport ##{id}", :moderation_report_rejected, subject: self, user: updater)
end
end
def reported_user
case model
when Comment, ForumPost
model.creator
when Dmail
model.from
else
raise NotImplementedError
end
end
def self.received_by(user)
where(model: Comment.where(creator: user)).or(where(model: ForumPost.where(creator: user))).or(where(model: Dmail.received.where(from: user)))
end
def self.search(params, current_user)
q = search_attributes(params, [:id, :created_at, :updated_at, :reason, :creator, :model, :status], current_user: current_user)
if params[:recipient_id].present?
q = q.received_by(User.search({ id: params[:recipient_id] }, current_user))
elsif params[:recipient_name].present?
q = q.received_by(User.search({ name_matches: params[:recipient_name] }, current_user))
end
q.apply_default_order(params)
end
def self.available_includes
[:creator, :model]
end
end