87 lines
		
	
	
	
		
			2.2 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			87 lines
		
	
	
	
		
			2.2 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # 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
 | |
|     doc = Nokogiri::HTML(value).at_xpath('//body')
 | |
| 
 | |
|     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
 |