Gab Social. All are welcome.

This commit is contained in:
robcolbert
2019-07-02 03:10:25 -04:00
commit bd0b5afc92
5366 changed files with 222812 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
# frozen_string_literal: true
class BlacklistedEmailValidator < ActiveModel::Validator
def validate(user)
return if user.valid_invitation?
@email = user.email
user.errors.add(:email, I18n.t('users.invalid_email')) if blocked_email?
end
private
def blocked_email?
on_blacklist? || not_on_whitelist?
end
def on_blacklist?
return true if EmailDomainBlock.block?(@email)
return false if Rails.configuration.x.email_domains_blacklist.blank?
domains = Rails.configuration.x.email_domains_blacklist.gsub('.', '\.')
regexp = Regexp.new("@(.+\\.)?(#{domains})", true)
@email =~ regexp
end
def not_on_whitelist?
return false if Rails.configuration.x.email_domains_whitelist.blank?
domains = Rails.configuration.x.email_domains_whitelist.gsub('.', '\.')
regexp = Regexp.new("@(.+\\.)?(#{domains})$", true)
@email !~ regexp
end
end

View File

@@ -0,0 +1,27 @@
# frozen_string_literal: true
class DisallowedHashtagsValidator < ActiveModel::Validator
def validate(status)
return unless status.local? && !status.reblog?
@status = status
tags = select_tags
status.errors.add(:text, I18n.t('statuses.disallowed_hashtags', tags: tags.join(', '), count: tags.size)) unless tags.empty?
end
private
def select_tags
tags = Extractor.extract_hashtags(@status.text)
tags.keep_if { |tag| disallowed_hashtags.include? tag.downcase }
end
def disallowed_hashtags
return @disallowed_hashtags if @disallowed_hashtags
@disallowed_hashtags = Setting.disallowed_hashtags.nil? ? [] : Setting.disallowed_hashtags
@disallowed_hashtags = @disallowed_hashtags.split(' ') if @disallowed_hashtags.is_a? String
@disallowed_hashtags = @disallowed_hashtags.map(&:downcase)
end
end

View File

@@ -0,0 +1,37 @@
# frozen_string_literal: true
require 'resolv'
class EmailMxValidator < ActiveModel::Validator
def validate(user)
user.errors.add(:email, I18n.t('users.invalid_email')) if invalid_mx?(user.email)
end
private
def invalid_mx?(value)
_, domain = value.split('@', 2)
return true if domain.nil?
hostnames = []
ips = []
Resolv::DNS.open do |dns|
dns.timeouts = 1
hostnames = dns.getresources(domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }
([domain] + hostnames).uniq.each do |hostname|
ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::A).to_a.map { |e| e.address.to_s })
ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::AAAA).to_a.map { |e| e.address.to_s })
end
end
ips.empty? || on_blacklist?(hostnames + ips)
end
def on_blacklist?(values)
EmailDomainBlock.where(domain: values.uniq).any?
end
end

View File

@@ -0,0 +1,14 @@
# frozen_string_literal: true
class ExistingUsernameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.blank?
if options[:multiple]
missing_usernames = value.split(',').map { |username| username.strip.gsub(/\A@/, '') }.map { |username| username unless Account.find_local(username) }.compact
record.errors.add(attribute, I18n.t('existing_username_validator.not_found_multiple', usernames: missing_usernames.join(', '))) if missing_usernames.any?
else
record.errors.add(attribute, I18n.t('existing_username_validator.not_found')) unless Account.find_local(value.strip.gsub(/\A@/, ''))
end
end
end

View File

@@ -0,0 +1,27 @@
# frozen_string_literal: true
class FollowLimitValidator < ActiveModel::Validator
LIMIT = ENV.fetch('MAX_FOLLOWS_THRESHOLD', 7_500).to_i
RATIO = ENV.fetch('MAX_FOLLOWS_RATIO', 1.1).to_f
def validate(follow)
return if follow.account.nil? || !follow.account.local?
follow.errors.add(:base, I18n.t('users.follow_limit_reached', limit: self.class.limit_for_account(follow.account))) if limit_reached?(follow.account)
end
class << self
def limit_for_account(account)
if account.following_count < LIMIT
LIMIT
else
[(account.followers_count * RATIO).round, LIMIT].max
end
end
end
private
def limit_reached?(account)
account.following_count >= self.class.limit_for_account(account)
end
end

View File

@@ -0,0 +1,20 @@
# frozen_string_literal: true
class HtmlValidator < ActiveModel::EachValidator
ERROR_RE = /Opening and ending tag mismatch|Unexpected end tag/
def validate_each(record, attribute, value)
return if value.blank?
errors = html_errors(value)
record.errors.add(attribute, I18n.t('html_validator.invalid_markup', error: errors.first.to_s)) unless errors.empty?
end
private
def html_errors(str)
fragment = Nokogiri::HTML.fragment(options[:wrap_with] ? "<#{options[:wrap_with]}>#{str}</#{options[:wrap_with]}>" : str)
fragment.errors.select { |error| ERROR_RE =~ error.message }
end
end

View File

@@ -0,0 +1,22 @@
# frozen_string_literal: true
class NoteLengthValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors.add(attribute, I18n.t('statuses.over_character_limit', max: options[:maximum])) if too_long?(value)
end
private
def too_long?(value)
countable_text(value).mb_chars.grapheme_length > options[:maximum]
end
def countable_text(value)
return '' if value.nil?
value.dup.tap do |new_text|
new_text.gsub!(FetchLinkCardService::URL_PATTERN, 'x' * 23)
new_text.gsub!(Account::MENTION_RE, '@\2')
end
end
end

View File

@@ -0,0 +1,19 @@
# frozen_string_literal: true
class PollValidator < ActiveModel::Validator
MAX_OPTIONS = 4
MAX_OPTION_CHARS = 25
MAX_EXPIRATION = 1.month.freeze
MIN_EXPIRATION = 5.minutes.freeze
def validate(poll)
current_time = Time.now.utc
poll.errors.add(:options, I18n.t('polls.errors.too_few_options')) unless poll.options.size > 1
poll.errors.add(:options, I18n.t('polls.errors.too_many_options', max: MAX_OPTIONS)) if poll.options.size > MAX_OPTIONS
poll.errors.add(:options, I18n.t('polls.errors.over_character_limit', max: MAX_OPTION_CHARS)) if poll.options.any? { |option| option.mb_chars.grapheme_length > MAX_OPTION_CHARS }
poll.errors.add(:options, I18n.t('polls.errors.duplicate_options')) unless poll.options.uniq.size == poll.options.size
poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_long')) if poll.expires_at.nil? || poll.expires_at - current_time > MAX_EXPIRATION
poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_short')) if poll.expires_at.present? && (poll.expires_at - current_time).ceil < MIN_EXPIRATION
end
end

View File

@@ -0,0 +1,35 @@
# frozen_string_literal: true
class StatusLengthValidator < ActiveModel::Validator
MAX_CHARS = 3000
def validate(status)
return unless status.local? && !status.reblog?
@status = status
status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if too_long?
end
private
def too_long?
countable_length > MAX_CHARS
end
def countable_length
total_text.mb_chars.grapheme_length
end
def total_text
[@status.spoiler_text, countable_text].join
end
def countable_text
return '' if @status.text.nil?
@status.text.dup.tap do |new_text|
new_text.gsub!(FetchLinkCardService::URL_PATTERN, 'x' * 23)
new_text.gsub!(Account::MENTION_RE, '@\2')
end
end
end

View File

@@ -0,0 +1,10 @@
# frozen_string_literal: true
class StatusPinValidator < ActiveModel::Validator
def validate(pin)
pin.errors.add(:base, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog?
pin.errors.add(:base, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id
pin.errors.add(:base, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility)
pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > 4 && pin.account.local?
end
end

View File

@@ -0,0 +1,14 @@
# frozen_string_literal: true
class UniqueUsernameValidator < ActiveModel::Validator
def validate(account)
return if account.username.nil?
normalized_username = account.username.downcase.delete('.')
scope = Account.where(domain: nil).where('lower(username) = ?', normalized_username)
scope = scope.where.not(id: account.id) if account.persisted?
account.errors.add(:username, :taken) if scope.exists?
end
end

View File

@@ -0,0 +1,23 @@
# frozen_string_literal: true
class UnreservedUsernameValidator < ActiveModel::Validator
def validate(account)
@username = account.username
return if @username.nil?
account.errors.add(:username, I18n.t('accounts.reserved_username')) if reserved_username?
end
private
def pam_controlled?
return false unless Devise.pam_authentication && Devise.pam_controlled_service
Rpam2.account(Devise.pam_controlled_service, @username).present?
end
def reserved_username?
return true if pam_controlled?
return false unless Setting.reserved_usernames
Setting.reserved_usernames.include?(@username.downcase)
end
end

View File

@@ -0,0 +1,14 @@
# frozen_string_literal: true
class UrlValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors.add(attribute, I18n.t('applications.invalid_url')) unless compliant?(value)
end
private
def compliant?(url)
parsed_url = Addressable::URI.parse(url)
parsed_url && %w(http https).include?(parsed_url.scheme) && parsed_url.host
end
end

View File

@@ -0,0 +1,13 @@
# frozen_string_literal: true
class VoteValidator < ActiveModel::Validator
def validate(vote)
vote.errors.add(:base, I18n.t('polls.errors.expired')) if vote.poll.expired?
if vote.poll.multiple? && vote.poll.votes.where(account: vote.account, choice: vote.choice).exists?
vote.errors.add(:base, I18n.t('polls.errors.already_voted'))
elsif !vote.poll.multiple? && vote.poll.votes.where(account: vote.account).exists?
vote.errors.add(:base, I18n.t('polls.errors.already_voted'))
end
end
end