class EmailValidator
EmailValidator class
def address_literal
def address_literal "\\[(?:#{ipv4}|#{ipv6})\\]" end
def alnum
def alnum '[[:alnum:]]' end
def alnumhy
def alnumhy "(?:#{alnum}|-)" end
def alpha
def alpha '[[:alpha:]]' end
def atom_char
def atom_char # The `atext` spec # We are looking at this without whitespace; no whitespace support here "[-#{alpha}#{alnum}+_!\"'#$%^&*{}/=?`|~]" end
def default_options
def default_options @@default_options end
def domain_label_pattern
splitting this up into separate regex pattern for performance; let's not
def domain_label_pattern "#{host_label_pattern}\\.#{tld_label_pattern}" end
def domain_part_is_correct_length
def domain_part_is_correct_length '(?=.{1,255}$)' end
def domain_part_pattern(options)
def domain_part_pattern(options) return regexp_safe_domain(options) unless options[:domain].nil? return fqdn_pattern if options[:require_fqdn] "#{domain_part_is_correct_length}(?:#{address_literal}|(?:#{host_label_pattern}\\.)*#{tld_label_pattern})" end
def fqdn_pattern
def fqdn_pattern "(?=.{1,255}$)(?:#{host_label_pattern}\\.)*#{domain_label_pattern}" end
def host_label_pattern
def host_label_pattern "#{label_is_correct_length}" \ "#{alnum}(?:#{alnumhy}{,61}#{alnum})?" end
def invalid?(value, options = {})
def invalid?(value, options = {}) !valid?(value, options) end
def ipv4
def ipv4 '\d{1,3}(?:\.\d{1,3}){3}' end
def ipv6
def ipv6 # only supporting full IPv6 addresses right now 'IPv6:[[:xdigit:]]{1,4}(?::[[:xdigit:]]{1,4}){7}' end
def label_is_correct_length
def label_is_correct_length '(?=[^.]{1,63}(?:\.|$))' end
def local_part_pattern
def local_part_pattern # the `dot-atom-text` spec, but with a 64 character limit "#{atom_char}(?:\\.?#{atom_char}){,63}" end
def loose_regexp(options = {})
def loose_regexp(options = {}) return /\A[^\s]+@[^\s]+\z/ if options[:domain].nil? /\A[^\s]+@#{domain_part_pattern(options)}\z/ end
def parse_options(options)
def parse_options(options) # `:strict` mode enables `:require_fqdn`, unless it is already explicitly disabled options[:require_fqdn] = true if options[:require_fqdn].nil? && options[:mode] == :strict default_options.merge(options) end
def regexp(options = {})
https://tools.ietf.org/html/rfc2822 : 3.2. Lexical Tokens, 3.4.1. Addr-spec specification
Refs:
def regexp(options = {}) options = parse_options(options) case options[:mode] when :loose loose_regexp(options) when :rfc rfc_regexp(options) when :strict options[:require_fqdn] = true strict_regexp(options) else fail EmailValidator::Error, "Validation mode '#{options[:mode]}' is not supported by EmailValidator" end end
def regexp_safe_domain(options)
def regexp_safe_domain(options) options[:domain].sub(/\./, '\.') end
def rfc_regexp(options = {})
def rfc_regexp(options = {}) /\A(?>#{local_part_pattern})(?:@#{domain_part_pattern(options)})?\z/i end
def strict_regexp(options = {})
def strict_regexp(options = {}) /\A(?>#{local_part_pattern})@#{domain_part_pattern(options)}\z/i end
def tld_label_pattern
> The ASCII label must consist entirely of letters (alphabetic characters a-z)
says the following starting at page 64:
Ref: ICANN Application Guidebook for new TLDs (June 2012)
While, techincally, TLDs can be numeric-only, this is not allowed by ICANN
def tld_label_pattern "#{alpha}{1,64}" end
def valid?(value, options = {})
def valid?(value, options = {}) options = parse_options(options) return true if value.nil? && options[:allow_nil] == true return false if value.nil? # quickly fail if domain is required but doesn't match return false unless options[:domain].nil? || value[/^.*@#{regexp_safe_domain(options)}$/] !!(value =~ regexp(options)) end
def validate_each(record, attribute, value)
def validate_each(record, attribute, value) options = @@default_options.merge(self.options) record.errors.add(attribute, options[:message] || :invalid) unless self.class.valid?(value, options) end