You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
94 lines
2.4 KiB
94 lines
2.4 KiB
# frozen_string_literal: true |
|
|
|
class Account::Field < ActiveModelSerializers::Model |
|
MAX_CHARACTERS_LOCAL = 255 |
|
MAX_CHARACTERS_COMPAT = 2_047 |
|
ACCEPTED_SCHEMES = %w(https).freeze |
|
|
|
attributes :name, :value, :verified_at, :account |
|
|
|
def initialize(account, attributes) |
|
# Keeping this as reference allows us to update the field on the account |
|
# from methods in this class, so that changes can be saved. |
|
@original_field = attributes |
|
@account = account |
|
|
|
super( |
|
name: sanitize(attributes['name']), |
|
value: sanitize(attributes['value']), |
|
verified_at: attributes['verified_at']&.to_datetime, |
|
) |
|
end |
|
|
|
def verified? |
|
verified_at.present? |
|
end |
|
|
|
def value_for_verification |
|
@value_for_verification ||= if account.local? |
|
value |
|
else |
|
extract_url_from_html |
|
end |
|
end |
|
|
|
def verifiable? |
|
return false if value_for_verification.blank? |
|
|
|
# This is slower than checking through a regular expression, but we |
|
# need to confirm that it's not an IDN domain. |
|
|
|
parsed_url = Addressable::URI.parse(value_for_verification) |
|
|
|
ACCEPTED_SCHEMES.include?(parsed_url.scheme) && |
|
parsed_url.user.nil? && |
|
parsed_url.password.nil? && |
|
parsed_url.host.present? && |
|
parsed_url.normalized_host == parsed_url.host && |
|
(parsed_url.path.empty? || parsed_url.path == parsed_url.normalized_path) |
|
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError |
|
false |
|
end |
|
|
|
def requires_verification? |
|
!verified? && verifiable? |
|
end |
|
|
|
def mark_verified! |
|
@original_field['verified_at'] = self.verified_at = Time.now.utc |
|
end |
|
|
|
def to_h |
|
{ name: name, value: value, verified_at: verified_at } |
|
end |
|
|
|
private |
|
|
|
def sanitize(str) |
|
str.strip[0, character_limit] |
|
end |
|
|
|
def character_limit |
|
account.local? ? MAX_CHARACTERS_LOCAL : MAX_CHARACTERS_COMPAT |
|
end |
|
|
|
def extract_url_from_html |
|
begin |
|
doc = Nokogiri::HTML5.fragment(value) |
|
rescue ArgumentError |
|
# This can happen if one of the Nokogumbo limits is encountered |
|
# Unfortunately, it does not use a more precise error class |
|
# nor allows more graceful handling |
|
return |
|
end |
|
|
|
return if doc.nil? |
|
return if doc.children.size != 1 |
|
|
|
element = doc.children.first |
|
|
|
return if element.name != 'a' || element['href'] != element.text |
|
|
|
element['href'] |
|
end |
|
end
|
|
|