Merge tag 'v4.2.0-beta2'

This commit is contained in:
bgme 2023-08-22 03:51:20 +08:00
commit 9e38d55101
3010 changed files with 81215 additions and 55173 deletions

View file

@ -4,13 +4,11 @@ class DomainValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.blank?
domain = begin
if options[:acct]
value.split('@').last
else
value
end
end
domain = if options[:acct]
value.split('@').last
else
value
end
record.errors.add(attribute, I18n.t('domain_validator.invalid_domain')) unless compliant?(domain)
end

View file

@ -6,7 +6,7 @@ class Ed25519KeyValidator < ActiveModel::EachValidator
key = Base64.decode64(value)
record.errors[attribute] << I18n.t('crypto.errors.invalid_key') unless verified?(key)
record.errors.add(attribute, I18n.t('crypto.errors.invalid_key')) unless verified?(key)
end
private

View file

@ -8,7 +8,7 @@ class Ed25519SignatureValidator < ActiveModel::EachValidator
signature = Base64.decode64(value)
message = option_to_value(record, :message)
record.errors[attribute] << I18n.t('crypto.errors.invalid_signature') unless verified?(verify_key, signature, message)
record.errors.add(attribute, I18n.t('crypto.errors.invalid_signature')) unless verified?(verify_key, signature, message)
end
private

View file

@ -8,9 +8,7 @@ class EmailMxValidator < ActiveModel::Validator
domain = get_domain(user.email)
if domain.blank?
user.errors.add(:email, :invalid)
elsif domain.include?('..')
if domain.blank? || domain.include?('..')
user.errors.add(:email, :invalid)
elsif !on_allowlist?(domain)
resolved_ips, resolved_domains = resolve_mx(domain)

View file

@ -4,15 +4,13 @@ class ExistingUsernameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.blank?
usernames_and_domains = begin
value.split(',').map do |str|
username, domain = str.strip.gsub(/\A@/, '').split('@', 2)
domain = nil if TagManager.instance.local_domain?(domain)
usernames_and_domains = value.split(',').filter_map do |str|
username, domain = str.strip.gsub(/\A@/, '').split('@', 2)
domain = nil if TagManager.instance.local_domain?(domain)
next if username.blank?
next if username.blank?
[str, username, domain]
end.compact
[str, username, domain]
end
usernames_with_no_accounts = usernames_and_domains.filter_map do |(str, username, domain)|

View file

@ -6,6 +6,7 @@ class FollowLimitValidator < ActiveModel::Validator
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

View file

@ -1,20 +0,0 @@
# 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.match?(error.message) }
end
end

View file

@ -1,48 +0,0 @@
# frozen_string_literal: true
require 'csv'
class ImportValidator < ActiveModel::Validator
KNOWN_HEADERS = [
'Account address',
'#domain',
'#uri',
].freeze
def validate(import)
return if import.type.blank? || import.data.blank?
# We parse because newlines could be part of individual rows. This
# runs on create so we should be reading the local file here before
# it is uploaded to object storage or moved anywhere...
csv_data = CSV.parse(import.data.queued_for_write[:original].read)
row_count = csv_data.size
row_count -= 1 if KNOWN_HEADERS.include?(csv_data.first&.first)
import.errors.add(:data, I18n.t('imports.errors.over_rows_processing_limit', count: ImportService::ROWS_PROCESSING_LIMIT)) if row_count > ImportService::ROWS_PROCESSING_LIMIT
case import.type
when 'following'
validate_following_import(import, row_count)
end
rescue CSV::MalformedCSVError
import.errors.add(:data, :malformed)
end
private
def validate_following_import(import, row_count)
base_limit = FollowLimitValidator.limit_for_account(import.account)
limit = begin
if import.overwrite?
base_limit
else
base_limit - import.account.following_count
end
end
import.errors.add(:data, I18n.t('users.follow_limit_reached', limit: base_limit)) if row_count > limit
end
end

View file

@ -4,18 +4,20 @@ class LanguageValidator < ActiveModel::EachValidator
include LanguagesHelper
def validate_each(record, attribute, value)
record.errors.add(attribute, :invalid) unless valid?(value)
@value = value
record.errors.add(attribute, :invalid) unless valid_locale_value?
end
private
def valid?(str)
if str.nil?
def valid_locale_value?
if @value.nil?
true
elsif str.is_a?(Array)
str.all? { |x| valid_locale?(x) }
elsif @value.is_a?(Array)
@value.all? { |x| valid_locale?(x) }
else
valid_locale?(str)
valid_locale?(@value)
end
end
end

View file

@ -45,7 +45,7 @@ class StatusLengthValidator < ActiveModel::Validator
def rewrite_entities(str, entities)
entities.sort_by! { |entity| entity[:indices].first }
result = ''.dup
result = +''
last_index = entities.reduce(0) do |index, entity|
result << str[index...entity[:indices].first]
@ -53,7 +53,7 @@ class StatusLengthValidator < ActiveModel::Validator
entity[:indices].last
end
result << str[last_index..-1]
result << str[last_index..]
result
end
end

View file

@ -13,12 +13,14 @@ class UnreservedUsernameValidator < ActiveModel::Validator
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

@ -1,16 +1,31 @@
# frozen_string_literal: true
class URLValidator < ActiveModel::EachValidator
VALID_SCHEMES = %w(http https).freeze
def validate_each(record, attribute, value)
record.errors.add(attribute, :invalid) unless compliant?(value)
@value = value
record.errors.add(attribute, :invalid) unless compliant_url?
end
private
def compliant?(url)
parsed_url = Addressable::URI.parse(url)
parsed_url && %w(http https).include?(parsed_url.scheme) && parsed_url.host
def compliant_url?
parsed_url.present? && valid_url_scheme? && valid_url_host?
end
def parsed_url
Addressable::URI.parse(@value)
rescue Addressable::URI::InvalidURIError
false
end
def valid_url_scheme?
VALID_SCHEMES.include?(parsed_url.scheme)
end
def valid_url_host?
parsed_url.host.present?
end
end

View file

@ -2,19 +2,27 @@
class VoteValidator < ActiveModel::Validator
def validate(vote)
vote.errors.add(:base, I18n.t('polls.errors.expired')) if vote.poll.expired?
vote.errors.add(:base, I18n.t('polls.errors.expired')) if vote.poll_expired?
vote.errors.add(:base, I18n.t('polls.errors.invalid_choice')) if invalid_choice?(vote)
vote.errors.add(:base, I18n.t('polls.errors.self_vote')) if self_vote?(vote)
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
vote.errors.add(:base, I18n.t('polls.errors.already_voted')) if additional_voting_not_allowed?(vote)
end
private
def additional_voting_not_allowed?(vote)
poll_multiple_and_already_voted?(vote) || poll_non_multiple_and_already_voted?(vote)
end
def poll_multiple_and_already_voted?(vote)
vote.poll_multiple? && already_voted_for_same_choice_on_multiple_poll?(vote)
end
def poll_non_multiple_and_already_voted?(vote)
!vote.poll_multiple? && already_voted_on_non_multiple_poll?(vote)
end
def invalid_choice?(vote)
vote.choice.negative? || vote.choice >= vote.poll.options.size
end
@ -22,4 +30,24 @@ class VoteValidator < ActiveModel::Validator
def self_vote?(vote)
vote.account_id == vote.poll.account_id
end
def already_voted_for_same_choice_on_multiple_poll?(vote)
if vote.persisted?
account_votes_on_same_poll(vote).where(choice: vote.choice).where.not(poll_votes: { id: vote }).exists?
else
account_votes_on_same_poll(vote).where(choice: vote.choice).exists?
end
end
def already_voted_on_non_multiple_poll?(vote)
if vote.persisted?
account_votes_on_same_poll(vote).where.not(poll_votes: { id: vote }).exists?
else
account_votes_on_same_poll(vote).exists?
end
end
def account_votes_on_same_poll(vote)
vote.poll.votes.where(account: vote.account)
end
end