emails: move EmailValidator into Danbooru::EmailAddress.
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# A utility class that represents an email address. A wrapper around Mail::Address
|
||||
# that adds extra utility methods for normalizing and validating email addresses.
|
||||
# that adds extra methods for validating email addresses, correcting addresses
|
||||
# containing typos, and canonicalizing multiple forms of the same address.
|
||||
#
|
||||
# @see https://www.rubydoc.info/gems/mail/Mail/Address
|
||||
# @see app/logical/email_address_type.rb
|
||||
@@ -13,13 +14,226 @@ module Danbooru
|
||||
# https://www.regular-expressions.info/email.html
|
||||
EMAIL_REGEX = /\A[a-z0-9._%+-]+@(?:[a-z0-9][a-z0-9-]{0,61}\.)+[a-z]{2,}\z/i
|
||||
|
||||
# Sites that ignore dots in email addresses, e.g. where `foo.bar@gmail.com` is the same as `foobar@gmail.com`.
|
||||
IGNORE_DOTS = %w[gmail.com]
|
||||
|
||||
# Sites that allow plus addressing, e.g. `test+nospam@gmail.com`.
|
||||
# @see https://en.wikipedia.org/wiki/Email_address#Subaddressing
|
||||
PLUS_ADDRESSING = %w[gmail.com hotmail.com outlook.com live.com]
|
||||
MINUS_ADDRESSING = %w[yahoo.com]
|
||||
|
||||
# Sites that have multiple domains mapping to the same logical email address.
|
||||
CANONICAL_DOMAINS = {
|
||||
"googlemail.com" => "gmail.com",
|
||||
"hotmail.com.ar" => "outlook.com",
|
||||
"hotmail.com.au" => "outlook.com",
|
||||
"hotmail.com.br" => "outlook.com",
|
||||
"hotmail.com.hk" => "outlook.com",
|
||||
"hotmail.com.tw" => "outlook.com",
|
||||
"hotmail.co.jp" => "outlook.com",
|
||||
"hotmail.co.nz" => "outlook.com",
|
||||
"hotmail.co.th" => "outlook.com",
|
||||
"hotmail.co.uk" => "outlook.com",
|
||||
"hotmail.com" => "outlook.com",
|
||||
"hotmail.be" => "outlook.com",
|
||||
"hotmail.ca" => "outlook.com",
|
||||
"hotmail.cl" => "outlook.com",
|
||||
"hotmail.de" => "outlook.com",
|
||||
"hotmail.dk" => "outlook.com",
|
||||
"hotmail.es" => "outlook.com",
|
||||
"hotmail.fi" => "outlook.com",
|
||||
"hotmail.fr" => "outlook.com",
|
||||
"hotmail.hu" => "outlook.com",
|
||||
"hotmail.it" => "outlook.com",
|
||||
"hotmail.my" => "outlook.com",
|
||||
"hotmail.nl" => "outlook.com",
|
||||
"hotmail.no" => "outlook.com",
|
||||
"hotmail.ru" => "outlook.com",
|
||||
"hotmail.sg" => "outlook.com",
|
||||
"hotmail.se" => "outlook.com",
|
||||
"live.com.au" => "outlook.com",
|
||||
"live.com.ar" => "outlook.com",
|
||||
"live.com.mx" => "outlook.com",
|
||||
"live.com.pt" => "outlook.com",
|
||||
"live.co.uk" => "outlook.com",
|
||||
"live.com" => "outlook.com",
|
||||
"live.at" => "outlook.com",
|
||||
"live.be" => "outlook.com",
|
||||
"live.ca" => "outlook.com",
|
||||
"live.cl" => "outlook.com",
|
||||
"live.cn" => "outlook.com",
|
||||
"live.de" => "outlook.com",
|
||||
"live.dk" => "outlook.com",
|
||||
"live.fr" => "outlook.com",
|
||||
"live.hk" => "outlook.com",
|
||||
"live.ie" => "outlook.com",
|
||||
"live.it" => "outlook.com",
|
||||
"live.jp" => "outlook.com",
|
||||
"live.nl" => "outlook.com",
|
||||
"live.no" => "outlook.com",
|
||||
"live.ru" => "outlook.com",
|
||||
"live.se" => "outlook.com",
|
||||
"msn.com" => "outlook.com",
|
||||
"outlook.com.ar" => "outlook.com",
|
||||
"outlook.com.au" => "outlook.com",
|
||||
"outlook.com.br" => "outlook.com",
|
||||
"outlook.co.id" => "outlook.com",
|
||||
"outlook.co.uk" => "outlook.com",
|
||||
"outlook.co.jp" => "outlook.com",
|
||||
"outlook.co.nz" => "outlook.com",
|
||||
"outlook.co.th" => "outlook.com",
|
||||
"outlook.at" => "outlook.com",
|
||||
"outlook.be" => "outlook.com",
|
||||
"outlook.ca" => "outlook.com",
|
||||
"outlook.cl" => "outlook.com",
|
||||
"outlook.cn" => "outlook.com",
|
||||
"outlook.de" => "outlook.com",
|
||||
"outlook.dk" => "outlook.com",
|
||||
"outlook.es" => "outlook.com",
|
||||
"outlook.fr" => "outlook.com",
|
||||
"outlook.ie" => "outlook.com",
|
||||
"outlook.it" => "outlook.com",
|
||||
"outlook.kr" => "outlook.com",
|
||||
"outlook.jp" => "outlook.com",
|
||||
"outlook.nl" => "outlook.com",
|
||||
"outlook.pt" => "outlook.com",
|
||||
"outlook.ru" => "outlook.com",
|
||||
"outlook.sa" => "outlook.com",
|
||||
"outlook.se" => "outlook.com",
|
||||
"yahoo.com.au" => "yahoo.com",
|
||||
"yahoo.com.ar" => "yahoo.com",
|
||||
"yahoo.com.br" => "yahoo.com",
|
||||
"yahoo.com.cn" => "yahoo.com",
|
||||
"yahoo.com.hk" => "yahoo.com",
|
||||
"yahoo.com.mx" => "yahoo.com",
|
||||
"yahoo.com.my" => "yahoo.com",
|
||||
"yahoo.com.ph" => "yahoo.com",
|
||||
"yahoo.com.sg" => "yahoo.com",
|
||||
"yahoo.com.tw" => "yahoo.com",
|
||||
"yahoo.com.vn" => "yahoo.com",
|
||||
"yahoo.co.id" => "yahoo.com",
|
||||
"yahoo.co.kr" => "yahoo.com",
|
||||
"yahoo.co.jp" => "yahoo.com",
|
||||
"yahoo.co.nz" => "yahoo.com",
|
||||
"yahoo.co.uk" => "yahoo.com",
|
||||
"yahoo.co.th" => "yahoo.com",
|
||||
"yahoo.ne.jp" => "yahoo.com",
|
||||
"yahoo.ca" => "yahoo.com",
|
||||
"yahoo.cn" => "yahoo.com",
|
||||
"yahoo.de" => "yahoo.com",
|
||||
"yahoo.dk" => "yahoo.com",
|
||||
"yahoo.es" => "yahoo.com",
|
||||
"yahoo.fr" => "yahoo.com",
|
||||
"yahoo.ie" => "yahoo.com",
|
||||
"yahoo.in" => "yahoo.com",
|
||||
"yahoo.it" => "yahoo.com",
|
||||
"yahoo.no" => "yahoo.com",
|
||||
"yahoo.se" => "yahoo.com",
|
||||
"ymail.com" => "yahoo.com",
|
||||
"126.com" => "163.com",
|
||||
"aim.com" => "aol.com",
|
||||
"gmx.com" => "gmx.net",
|
||||
"gmx.at" => "gmx.net",
|
||||
"gmx.ch" => "gmx.net",
|
||||
"gmx.de" => "gmx.net",
|
||||
"gmx.fr" => "gmx.net",
|
||||
"gmx.us" => "gmx.net",
|
||||
"pm.me" => "protonmail.com",
|
||||
"protonmail.ch" => "protonmail.com",
|
||||
"proton.me" => "protonmail.com",
|
||||
"tuta.io" => "tutanota.com",
|
||||
"email.com" => "mail.com",
|
||||
"me.com" => "icloud.com",
|
||||
"ya.ru" => "yandex.ru",
|
||||
"yandex.com" => "yandex.ru",
|
||||
"yandex.by" => "yandex.ru",
|
||||
"yandex.ua" => "yandex.ru",
|
||||
"yandex.kz" => "yandex.ru",
|
||||
"inbox.ru" => "mail.ru",
|
||||
"bk.ru" => "mail.ru",
|
||||
"list.ru" => "mail.ru",
|
||||
"internet.ru" => "mail.ru",
|
||||
"hanmail.net" => "daum.net",
|
||||
}
|
||||
|
||||
# A list of domains known not to be disposable.
|
||||
#
|
||||
# @see https://www.mailboxvalidator.com/domain
|
||||
NONDISPOSABLE_DOMAINS = %w[
|
||||
gmail.com
|
||||
outlook.com
|
||||
yahoo.com
|
||||
aol.com
|
||||
comcast.net
|
||||
att.net
|
||||
bellsouth.net
|
||||
cox.net
|
||||
sbcglobal.net
|
||||
verizon.net
|
||||
icloud.com
|
||||
rocketmail.com
|
||||
windowslive.com
|
||||
qq.com
|
||||
vip.qq.com
|
||||
sina.com
|
||||
naver.com
|
||||
163.com
|
||||
daum.net
|
||||
mail.goo.ne.jp
|
||||
nate.com
|
||||
mail.com
|
||||
protonmail.com
|
||||
gmx.net
|
||||
web.de
|
||||
freenet.de
|
||||
o2.pl
|
||||
op.pl
|
||||
wp.pl
|
||||
interia.pl
|
||||
mail.ru
|
||||
yandex.ru
|
||||
rambler.ru
|
||||
abv.bg
|
||||
seznam.cz
|
||||
libero.it
|
||||
laposte.net
|
||||
free.fr
|
||||
orange.fr
|
||||
citromail.hu
|
||||
ukr.net
|
||||
t-online.de
|
||||
inbox.lv
|
||||
luukku.com
|
||||
lycos.com
|
||||
tlen.pl
|
||||
infoseek.jp
|
||||
excite.co.jp
|
||||
mac.com
|
||||
wanadoo.fr
|
||||
ezweb.ne.jp
|
||||
arcor.de
|
||||
docomo.ne.jp
|
||||
earthlink.net
|
||||
charter.net
|
||||
hushmail.com
|
||||
inbox.com
|
||||
juno.com
|
||||
shaw.ca
|
||||
walla.com
|
||||
tutanota.com
|
||||
foxmail.com
|
||||
vivaldi.net
|
||||
fastmail.com
|
||||
relay.firefox.com
|
||||
]
|
||||
|
||||
# @return [String] The original email address as a string.
|
||||
attr_reader :address
|
||||
|
||||
# @return [Mail::Address] The parsed email address.
|
||||
attr_reader :parsed_address
|
||||
|
||||
delegate :local, to: :parsed_address
|
||||
delegate :local, :domain, to: :parsed_address
|
||||
alias_method :name, :local
|
||||
alias_method :to_s, :address
|
||||
|
||||
@@ -27,10 +241,10 @@ module Danbooru
|
||||
#
|
||||
# @param string [String, Danbooru::EmailAddress]
|
||||
def initialize(string)
|
||||
raise Error, "#{string} is not a valid email address" if !string.match?(EMAIL_REGEX)
|
||||
raise Error, "#{string} is not a valid email address" if !self.class.is_valid?(string)
|
||||
|
||||
@address = string.to_s
|
||||
@parsed_address = Mail::Address.new(parsed_address)
|
||||
@parsed_address = Mail::Address.new(address)
|
||||
end
|
||||
|
||||
# Parse a string into an email address, or return nil if the string is not a syntactically valid email address.
|
||||
@@ -44,11 +258,11 @@ module Danbooru
|
||||
end
|
||||
|
||||
# Parse a string into an email address while attempting to fix common typos and mistakes, or return
|
||||
# nil if the string can't be normalized into a valid email address.
|
||||
# nil if the string can't be corrected into a valid email address.
|
||||
#
|
||||
# @param address [String]
|
||||
# @return [Danbooru::EmailAddress]
|
||||
def self.normalize(address)
|
||||
def self.correct(address)
|
||||
address = address.gsub(/[[:space:]]+/, " ").strip
|
||||
|
||||
address = address.gsub(/[\\\/]$/, '') # @qq.com\ -> @qq.com, @web.de/ -> @web.de
|
||||
@@ -100,16 +314,101 @@ module Danbooru
|
||||
parse(address)
|
||||
end
|
||||
|
||||
# @return [Danbooru::EmailAddress] The email address, normalized to fix typos.
|
||||
def normalized_address
|
||||
Danbooru::EmailAddress.normalize(address)
|
||||
# Returns true if the string is a syntactically valid email address.
|
||||
#
|
||||
# @param address [String] The email address.
|
||||
# @return [Boolean] True if the email address is syntactically valid.
|
||||
def self.is_valid?(address)
|
||||
address.to_s.match?(EMAIL_REGEX)
|
||||
end
|
||||
|
||||
# @return [PublicSuffix::Domain] The domain part of the email address.
|
||||
def domain
|
||||
@domain ||= PublicSuffix.parse(parsed_address.domain)
|
||||
rescue PublicSuffix::DomainNotAllowed
|
||||
nil
|
||||
concerning :DeliverableMethods do
|
||||
# Returns true if the email address can't receive mail. Checks that the domain exists, that it has a valid MX record,
|
||||
# that the mail server exists, and that it responds successfully to the RCPT TO command for the given address.
|
||||
#
|
||||
# @param from_address [String] The from address to use when connecting to the mail server.
|
||||
# @param timeout [Integer] The network timeout when connecting to the mail server.
|
||||
# @param allow_smtp [Boolean] If true, check if the mail server responds to the RCPT TO command. Disabled by default because many ISPs and server providers block port 25.
|
||||
# @return [Boolean] True if the email address is definitely undeliverable. False if the address is eligible for delivery. Delivery could
|
||||
# still fail if the mailbox doesn't exist and the server lied to the RCPT TO command.
|
||||
def undeliverable?(from_address: Danbooru.config.contact_email, timeout: 3, allow_smtp: false)
|
||||
mail_server = mx_domain(timeout: timeout)
|
||||
return true if mail_server.nil?
|
||||
|
||||
return false if !allow_smtp
|
||||
smtp = Net::SMTP.new(mail_server)
|
||||
smtp.read_timeout = timeout
|
||||
smtp.open_timeout = timeout
|
||||
|
||||
from_domain = Danbooru::EmailAddress.new(from_address).domain.to_s
|
||||
smtp.start(from_domain) do |conn|
|
||||
conn.mailfrom(from_address)
|
||||
|
||||
# Net::SMTPFatalError is raised if RCPT TO returns a 5xx error.
|
||||
response = conn.rcptto(to_address) rescue $!
|
||||
return response.is_a?(Net::SMTPFatalError)
|
||||
end
|
||||
rescue
|
||||
false
|
||||
end
|
||||
|
||||
# Perform a DNS MX record lookup of the domain and return the name of the mail server, if it exists.
|
||||
#
|
||||
# @param timeout [Integer] The network timeout when resolving the domain.
|
||||
# @return [String] The DNS name of the mail server.
|
||||
def mx_domain(timeout: nil)
|
||||
dns = Resolv::DNS.new
|
||||
dns.timeouts = timeout
|
||||
response = dns.getresource(domain.to_s, Resolv::DNS::Resource::IN::MX)
|
||||
|
||||
response.exchange.to_s
|
||||
rescue Resolv::ResolvError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Returns true if the email address is not a disposable or throwaway address (it comes from a well-known email provider).
|
||||
# Returns false if the address is potentially disposable (it comes from an unknown email provider, or a personal domain).
|
||||
def is_nondisposable?
|
||||
domain.to_s.in?(NONDISPOSABLE_DOMAINS)
|
||||
end
|
||||
|
||||
# @return [Danbooru::EmailAddress] The email address with typos corrected, e.g. "foo@gamil.com" => "foo@gmail.com".
|
||||
def corrected_address
|
||||
Danbooru::EmailAddress.correct(address)
|
||||
end
|
||||
|
||||
# @return [Danbooru::EmailAddress] The email address converted into canonical form, e.g. "Foo.Bar+nospam@googlemail.com" => "foobar@gmail.com".
|
||||
def canonicalized_address
|
||||
Danbooru::EmailAddress.new("#{canonical_name}@#{canonical_domain}")
|
||||
end
|
||||
|
||||
# @return [String] The name with the subaddress and periods removed, e.g. "Foo.Bar+nospam@gmail.com" => "foobar".
|
||||
def canonical_name
|
||||
name = name_and_subaddress.first
|
||||
name = name.delete(".") if canonical_domain.in?(IGNORE_DOTS)
|
||||
name.downcase
|
||||
end
|
||||
|
||||
# @return [String, nil] The part of the name after the `+` or `-`, e.g. "foo+nospam@gmail.com" => "nospam".
|
||||
def subaddress
|
||||
name_and_subaddress.second
|
||||
end
|
||||
|
||||
# @return [Array<String, String>] The address split into the name and the subaddress, e.g. "foo+nospam@gmail.com" => ["foo", "nospam"]
|
||||
def name_and_subaddress
|
||||
if canonical_domain.in?(PLUS_ADDRESSING)
|
||||
name.split("+")
|
||||
elsif canonical_domain.in?(MINUS_ADDRESSING)
|
||||
name.split("-")
|
||||
else
|
||||
[name, nil]
|
||||
end
|
||||
end
|
||||
|
||||
# @return [String] The primary domain for the site, if the site has multiple domains, e.g. "googlemail.com" => "gmail.com".
|
||||
def canonical_domain
|
||||
@canonical_domain ||= CANONICAL_DOMAINS.fetch(domain.to_s, domain.to_s)
|
||||
end
|
||||
|
||||
def as_json
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'resolv'
|
||||
|
||||
# Validates that an email address is well-formed, is deliverable, and is not a
|
||||
# disposable or throwaway email address. Also normalizes equivalent addresses to
|
||||
# a single canonical form, so that users can't use different forms of the same
|
||||
# address to register multiple accounts.
|
||||
module EmailValidator
|
||||
module_function
|
||||
|
||||
# https://www.regular-expressions.info/email.html
|
||||
EMAIL_REGEX = /\A[a-z0-9._%+-]+@(?:[a-z0-9][a-z0-9-]{0,61}\.)+[a-z]{2,}\z/i
|
||||
|
||||
# Sites that ignore dots in email addresses, e.g. where `te.st@gmail.com` is
|
||||
# the same as `test@gmail.com`.
|
||||
IGNORE_DOTS = %w[gmail.com]
|
||||
|
||||
# Sites that allow plus addressing, e.g. `test+nospam@gmail.com`.
|
||||
# @see https://en.wikipedia.org/wiki/Email_address#Subaddressing
|
||||
IGNORE_PLUS_ADDRESSING = %w[gmail.com hotmail.com outlook.com live.com]
|
||||
IGNORE_MINUS_ADDRESSING = %w[yahoo.com]
|
||||
|
||||
# Sites that have multiple domains mapping to the same logical email address.
|
||||
CANONICAL_DOMAINS = {
|
||||
"googlemail.com" => "gmail.com",
|
||||
"hotmail.com.ar" => "outlook.com",
|
||||
"hotmail.com.au" => "outlook.com",
|
||||
"hotmail.com.br" => "outlook.com",
|
||||
"hotmail.com.hk" => "outlook.com",
|
||||
"hotmail.com.tw" => "outlook.com",
|
||||
"hotmail.co.jp" => "outlook.com",
|
||||
"hotmail.co.nz" => "outlook.com",
|
||||
"hotmail.co.th" => "outlook.com",
|
||||
"hotmail.co.uk" => "outlook.com",
|
||||
"hotmail.com" => "outlook.com",
|
||||
"hotmail.be" => "outlook.com",
|
||||
"hotmail.ca" => "outlook.com",
|
||||
"hotmail.cl" => "outlook.com",
|
||||
"hotmail.de" => "outlook.com",
|
||||
"hotmail.dk" => "outlook.com",
|
||||
"hotmail.es" => "outlook.com",
|
||||
"hotmail.fi" => "outlook.com",
|
||||
"hotmail.fr" => "outlook.com",
|
||||
"hotmail.hu" => "outlook.com",
|
||||
"hotmail.it" => "outlook.com",
|
||||
"hotmail.my" => "outlook.com",
|
||||
"hotmail.nl" => "outlook.com",
|
||||
"hotmail.no" => "outlook.com",
|
||||
"hotmail.ru" => "outlook.com",
|
||||
"hotmail.sg" => "outlook.com",
|
||||
"hotmail.se" => "outlook.com",
|
||||
"live.com.au" => "outlook.com",
|
||||
"live.com.ar" => "outlook.com",
|
||||
"live.com.mx" => "outlook.com",
|
||||
"live.com.pt" => "outlook.com",
|
||||
"live.co.uk" => "outlook.com",
|
||||
"live.com" => "outlook.com",
|
||||
"live.at" => "outlook.com",
|
||||
"live.be" => "outlook.com",
|
||||
"live.ca" => "outlook.com",
|
||||
"live.cl" => "outlook.com",
|
||||
"live.cn" => "outlook.com",
|
||||
"live.de" => "outlook.com",
|
||||
"live.dk" => "outlook.com",
|
||||
"live.fr" => "outlook.com",
|
||||
"live.hk" => "outlook.com",
|
||||
"live.ie" => "outlook.com",
|
||||
"live.it" => "outlook.com",
|
||||
"live.jp" => "outlook.com",
|
||||
"live.nl" => "outlook.com",
|
||||
"live.no" => "outlook.com",
|
||||
"live.ru" => "outlook.com",
|
||||
"live.se" => "outlook.com",
|
||||
"msn.com" => "outlook.com",
|
||||
"outlook.com.ar" => "outlook.com",
|
||||
"outlook.com.au" => "outlook.com",
|
||||
"outlook.com.br" => "outlook.com",
|
||||
"outlook.co.id" => "outlook.com",
|
||||
"outlook.co.uk" => "outlook.com",
|
||||
"outlook.co.jp" => "outlook.com",
|
||||
"outlook.co.nz" => "outlook.com",
|
||||
"outlook.co.th" => "outlook.com",
|
||||
"outlook.at" => "outlook.com",
|
||||
"outlook.be" => "outlook.com",
|
||||
"outlook.ca" => "outlook.com",
|
||||
"outlook.cl" => "outlook.com",
|
||||
"outlook.cn" => "outlook.com",
|
||||
"outlook.de" => "outlook.com",
|
||||
"outlook.dk" => "outlook.com",
|
||||
"outlook.es" => "outlook.com",
|
||||
"outlook.fr" => "outlook.com",
|
||||
"outlook.ie" => "outlook.com",
|
||||
"outlook.it" => "outlook.com",
|
||||
"outlook.kr" => "outlook.com",
|
||||
"outlook.jp" => "outlook.com",
|
||||
"outlook.nl" => "outlook.com",
|
||||
"outlook.pt" => "outlook.com",
|
||||
"outlook.ru" => "outlook.com",
|
||||
"outlook.sa" => "outlook.com",
|
||||
"outlook.se" => "outlook.com",
|
||||
"yahoo.com.au" => "yahoo.com",
|
||||
"yahoo.com.ar" => "yahoo.com",
|
||||
"yahoo.com.br" => "yahoo.com",
|
||||
"yahoo.com.cn" => "yahoo.com",
|
||||
"yahoo.com.hk" => "yahoo.com",
|
||||
"yahoo.com.mx" => "yahoo.com",
|
||||
"yahoo.com.my" => "yahoo.com",
|
||||
"yahoo.com.ph" => "yahoo.com",
|
||||
"yahoo.com.sg" => "yahoo.com",
|
||||
"yahoo.com.tw" => "yahoo.com",
|
||||
"yahoo.com.vn" => "yahoo.com",
|
||||
"yahoo.co.id" => "yahoo.com",
|
||||
"yahoo.co.kr" => "yahoo.com",
|
||||
"yahoo.co.jp" => "yahoo.com",
|
||||
"yahoo.co.nz" => "yahoo.com",
|
||||
"yahoo.co.uk" => "yahoo.com",
|
||||
"yahoo.co.th" => "yahoo.com",
|
||||
"yahoo.ne.jp" => "yahoo.com",
|
||||
"yahoo.ca" => "yahoo.com",
|
||||
"yahoo.cn" => "yahoo.com",
|
||||
"yahoo.de" => "yahoo.com",
|
||||
"yahoo.dk" => "yahoo.com",
|
||||
"yahoo.es" => "yahoo.com",
|
||||
"yahoo.fr" => "yahoo.com",
|
||||
"yahoo.ie" => "yahoo.com",
|
||||
"yahoo.in" => "yahoo.com",
|
||||
"yahoo.it" => "yahoo.com",
|
||||
"yahoo.no" => "yahoo.com",
|
||||
"yahoo.se" => "yahoo.com",
|
||||
"ymail.com" => "yahoo.com",
|
||||
"126.com" => "163.com",
|
||||
"aim.com" => "aol.com",
|
||||
"gmx.com" => "gmx.net",
|
||||
"gmx.at" => "gmx.net",
|
||||
"gmx.ch" => "gmx.net",
|
||||
"gmx.de" => "gmx.net",
|
||||
"gmx.fr" => "gmx.net",
|
||||
"gmx.us" => "gmx.net",
|
||||
"pm.me" => "protonmail.com",
|
||||
"protonmail.ch" => "protonmail.com",
|
||||
"proton.me" => "protonmail.com",
|
||||
"tuta.io" => "tutanota.com",
|
||||
"email.com" => "mail.com",
|
||||
"me.com" => "icloud.com",
|
||||
"ya.ru" => "yandex.ru",
|
||||
"yandex.com" => "yandex.ru",
|
||||
"yandex.by" => "yandex.ru",
|
||||
"yandex.ua" => "yandex.ru",
|
||||
"yandex.kz" => "yandex.ru",
|
||||
"inbox.ru" => "mail.ru",
|
||||
"bk.ru" => "mail.ru",
|
||||
"list.ru" => "mail.ru",
|
||||
"internet.ru" => "mail.ru",
|
||||
"hanmail.net" => "daum.net",
|
||||
}
|
||||
|
||||
# A list of domains known not to be disposable. A user's email must be on
|
||||
# this list to unrestrict their account. If a user is Restricted and their
|
||||
# email is not in this list, then it's assumed to be disposable and can't be
|
||||
# used to unrestrict their account even if they verify their email address.
|
||||
#
|
||||
# https://www.mailboxvalidator.com/domain
|
||||
NONDISPOSABLE_DOMAINS = %w[
|
||||
gmail.com
|
||||
outlook.com
|
||||
yahoo.com
|
||||
aol.com
|
||||
comcast.net
|
||||
att.net
|
||||
bellsouth.net
|
||||
cox.net
|
||||
sbcglobal.net
|
||||
verizon.net
|
||||
icloud.com
|
||||
rocketmail.com
|
||||
windowslive.com
|
||||
qq.com
|
||||
vip.qq.com
|
||||
sina.com
|
||||
naver.com
|
||||
163.com
|
||||
daum.net
|
||||
mail.goo.ne.jp
|
||||
nate.com
|
||||
mail.com
|
||||
protonmail.com
|
||||
gmx.net
|
||||
web.de
|
||||
freenet.de
|
||||
o2.pl
|
||||
op.pl
|
||||
wp.pl
|
||||
interia.pl
|
||||
mail.ru
|
||||
yandex.ru
|
||||
rambler.ru
|
||||
abv.bg
|
||||
seznam.cz
|
||||
libero.it
|
||||
laposte.net
|
||||
free.fr
|
||||
orange.fr
|
||||
citromail.hu
|
||||
ukr.net
|
||||
t-online.de
|
||||
inbox.lv
|
||||
luukku.com
|
||||
lycos.com
|
||||
tlen.pl
|
||||
infoseek.jp
|
||||
excite.co.jp
|
||||
mac.com
|
||||
wanadoo.fr
|
||||
ezweb.ne.jp
|
||||
arcor.de
|
||||
docomo.ne.jp
|
||||
earthlink.net
|
||||
charter.net
|
||||
hushmail.com
|
||||
inbox.com
|
||||
juno.com
|
||||
shaw.ca
|
||||
walla.com
|
||||
tutanota.com
|
||||
foxmail.com
|
||||
vivaldi.net
|
||||
fastmail.com
|
||||
relay.firefox.com
|
||||
]
|
||||
|
||||
# Returns true if it's okay to connect to port 25. Disabled outside of
|
||||
# production because many home ISPs blackhole port 25.
|
||||
def smtp_enabled?
|
||||
Rails.env.production?
|
||||
end
|
||||
|
||||
# Normalize an email address by stripping out plus addressing and dots, if
|
||||
# applicable, and rewriting the domain to a canonical domain.
|
||||
# @param address [String] the email address to normalize
|
||||
# @return [String] the normalized address
|
||||
def normalize(address)
|
||||
return nil unless address.count("@") == 1
|
||||
|
||||
name, domain = address.downcase.split("@")
|
||||
|
||||
domain = CANONICAL_DOMAINS.fetch(domain, domain)
|
||||
name = name.delete(".") if domain.in?(IGNORE_DOTS)
|
||||
name = name.gsub(/\+.*\z/, "") if domain.in?(IGNORE_PLUS_ADDRESSING)
|
||||
name = name.gsub(/-.*\z/, "") if domain.in?(IGNORE_MINUS_ADDRESSING)
|
||||
|
||||
"#{name}@#{domain}"
|
||||
end
|
||||
|
||||
# Returns true if the email address is correctly formatted.
|
||||
# @param [String] the email address
|
||||
# @return [Boolean]
|
||||
def is_valid?(address)
|
||||
address.match?(EMAIL_REGEX)
|
||||
end
|
||||
|
||||
# Returns true if the email is a throwaway or disposable email address.
|
||||
# @param [String] the email address
|
||||
# @return [Boolean]
|
||||
def is_restricted?(address)
|
||||
domain = Mail::Address.new(address).domain
|
||||
!domain.in?(NONDISPOSABLE_DOMAINS)
|
||||
rescue Mail::Field::IncompleteParseError
|
||||
true
|
||||
end
|
||||
|
||||
# Returns true if the email can't be delivered. Checks if the domain has an MX
|
||||
# record and responds to the RCPT TO command.
|
||||
# @param to_address [String] the email address to check
|
||||
# @param from_address [String] the email address to check from
|
||||
# @return [Boolean]
|
||||
def undeliverable?(to_address, from_address: Danbooru.config.contact_email, timeout: 3)
|
||||
mail_server = mx_domain(to_address, timeout: timeout)
|
||||
mail_server.nil? || rcpt_to_failed?(to_address, from_address, mail_server, timeout: timeout)
|
||||
rescue
|
||||
false
|
||||
end
|
||||
|
||||
# Returns true if the email can't be delivered. Sends a RCPT TO command over
|
||||
# port 25 to check if the mailbox exists.
|
||||
# @param to_address [String] the email address to check
|
||||
# @param from_address [String] the email address to check from
|
||||
# @param mail_server [String] the DNS name of the SMTP server
|
||||
# @param timeout [Integer] the network timeout
|
||||
# @return [Boolean]
|
||||
def rcpt_to_failed?(to_address, from_address, mail_server, timeout: nil)
|
||||
return false unless smtp_enabled?
|
||||
|
||||
from_domain = Mail::Address.new(from_address).domain
|
||||
|
||||
smtp = Net::SMTP.new(mail_server)
|
||||
smtp.read_timeout = timeout
|
||||
smtp.open_timeout = timeout
|
||||
|
||||
smtp.start(from_domain) do |conn|
|
||||
conn.mailfrom(from_address)
|
||||
|
||||
# Net::SMTPFatalError is raised if RCPT TO returns a 5xx error.
|
||||
response = conn.rcptto(to_address) rescue $!
|
||||
return response.is_a?(Net::SMTPFatalError)
|
||||
end
|
||||
end
|
||||
|
||||
# Does a DNS MX record lookup of the domain in the email address and returns the
|
||||
# name of the mail server, if it exists.
|
||||
# @param to_address [String] the email address to check
|
||||
# @param timeout [Integer] the network timeout
|
||||
# @return [String] the DNS name of the mail server
|
||||
def mx_domain(to_address, timeout: nil)
|
||||
domain = Mail::Address.new(to_address).domain
|
||||
|
||||
dns = Resolv::DNS.new
|
||||
dns.timeouts = timeout
|
||||
response = dns.getresource(domain, Resolv::DNS::Resource::IN::MX)
|
||||
|
||||
response.exchange.to_s
|
||||
rescue Resolv::ResolvError
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -6,7 +6,7 @@ class EmailAddress < ApplicationRecord
|
||||
attribute :address
|
||||
attribute :normalized_address
|
||||
|
||||
validates :address, presence: true, format: { message: "is invalid", with: EmailValidator::EMAIL_REGEX }
|
||||
validates :address, presence: true, format: { message: "is invalid", with: Danbooru::EmailAddress::EMAIL_REGEX }
|
||||
validates :normalized_address, presence: true, uniqueness: true
|
||||
validates :user_id, uniqueness: true
|
||||
validate :validate_deliverable, on: :deliverable
|
||||
@@ -20,25 +20,21 @@ class EmailAddress < ApplicationRecord
|
||||
end
|
||||
|
||||
def address=(value)
|
||||
value = Danbooru::EmailAddress.normalize(value)&.to_s || value
|
||||
self.normalized_address = EmailValidator.normalize(value) || address
|
||||
value = Danbooru::EmailAddress.correct(value)&.to_s || value
|
||||
self.normalized_address = Danbooru::EmailAddress.parse(value)&.canonicalized_address&.to_s || value
|
||||
super
|
||||
end
|
||||
|
||||
def is_restricted?
|
||||
EmailValidator.is_restricted?(normalized_address)
|
||||
!Danbooru::EmailAddress.new(normalized_address).is_nondisposable?
|
||||
end
|
||||
|
||||
def is_normalized?
|
||||
address == normalized_address
|
||||
end
|
||||
|
||||
def is_valid?
|
||||
EmailValidator.is_valid?(address)
|
||||
end
|
||||
|
||||
def self.restricted(restricted = true)
|
||||
domains = EmailValidator::NONDISPOSABLE_DOMAINS
|
||||
domains = Danbooru::EmailAddress::NONDISPOSABLE_DOMAINS
|
||||
domain_regex = domains.map { |domain| Regexp.escape(domain) }.join("|")
|
||||
|
||||
if restricted.to_s.truthy?
|
||||
@@ -59,7 +55,7 @@ class EmailAddress < ApplicationRecord
|
||||
end
|
||||
|
||||
def validate_deliverable
|
||||
if EmailValidator.undeliverable?(address)
|
||||
if Danbooru::EmailAddress.new(address).undeliverable?(allow_smtp: Rails.env.production?)
|
||||
errors.add(:address, "is invalid or does not exist")
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user