Module: ValidatesEmailFormatOf

Defined in:
lib/validates_email_format_of.rb,
lib/validates_email_format_of/railtie.rb,
lib/validates_email_format_of/version.rb

Defined Under Namespace

Classes: Railtie

Constant Summary collapse

LocalPartSpecialChars =
/[\!\#\$\%\&\'\*\-\/\=\?\+\-\^\_\`\{\|\}\~]/
DEFAULT_MESSAGE =

Validates whether the specified value is a valid email address. Returns nil if the value is valid, otherwise returns an array containing one or more validation error messages.

Configuration options:

  • message - A custom error message (default is: “does not appear to be valid”)

  • check_mx - Check for MX records (default is false)

  • mx_message - A custom error message when an MX record validation fails (default is: “is not routable.”)

  • with The regex to use for validating the format of the email address (deprecated)

  • local_length Maximum number of characters allowed in the local part (default is 64)

  • domain_length Maximum number of characters allowed in the domain part (default is 255)

"does not appear to be valid"
DEFAULT_MX_MESSAGE =
"is not routable"
VERSION =
'1.6.0'

Class Method Summary collapse

Class Method Details

.default_messageObject



34
35
36
# File 'lib/validates_email_format_of.rb', line 34

def self.default_message
  defined?(I18n) ? I18n.t(:invalid_email_address, :scope => [:activemodel, :errors, :messages], :default => DEFAULT_MESSAGE) : DEFAULT_MESSAGE
end

.load_i18n_localesObject



5
6
7
8
# File 'lib/validates_email_format_of.rb', line 5

def self.load_i18n_locales
  require 'i18n'
  I18n.load_path += Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'locales', '*.yml')))
end

.validate_domain_part_syntax(domain) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/validates_email_format_of.rb', line 121

def self.validate_domain_part_syntax(domain)
  parts = domain.downcase.split('.', -1)

  return false if parts.length <= 1 # Only one domain part

  # Empty parts (double period) or invalid chars
  return false if parts.any? {
    |part|
      part.nil? or
      part.empty? or
      not part =~ /\A[[:alnum:]\-]+\Z/ or
      part[0,1] == '-' or part[-1,1] == '-' # hyphen at beginning or end of part
  }

  # ipv4
  return true if parts.length == 4 and parts.all? { |part| part =~ /\A[0-9]+\Z/ and part.to_i.between?(0, 255) }

  return false if parts[-1].length < 2 or not parts[-1] =~ /[a-z\-]/ # TLD is too short or does not contain a char or hyphen

  return true
end

.validate_email_domain(email) ⇒ Object



14
15
16
17
18
19
20
# File 'lib/validates_email_format_of.rb', line 14

def self.validate_email_domain(email)
  domain = email.to_s.downcase.match(/\@(.+)/)[1]
  Resolv::DNS.open do |dns|
    @mx = dns.getresources(domain, Resolv::DNS::Resource::IN::MX) + dns.getresources(domain, Resolv::DNS::Resource::IN::A)
  end
  @mx.size > 0 ? true : false
end

.validate_email_format(email, options = {}) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/validates_email_format_of.rb', line 38

def self.validate_email_format(email, options={})
    default_options = { :message => default_message,
                        :check_mx => false,
                        :mx_message => defined?(I18n) ? I18n.t(:email_address_not_routable, :scope => [:activemodel, :errors, :messages], :default => DEFAULT_MX_MESSAGE) : DEFAULT_MX_MESSAGE,
                        :domain_length => 255,
                        :local_length => 64
                        }
    opts = options.merge(default_options) {|key, old, new| old}  # merge the default options into the specified options, retaining all specified options

    email = email.strip if email

    begin
      domain, local = email.reverse.split('@', 2)
    rescue
      return [ opts[:message] ]
    end

    # need local and domain parts
    return [ opts[:message] ] unless local and not local.empty? and domain and not domain.empty?

    # check lengths
    return [ opts[:message] ] unless domain.length <= opts[:domain_length] and local.length <= opts[:local_length]

    local.reverse!
    domain.reverse!

    if opts.has_key?(:with) # holdover from versions <= 1.4.7
      return [ opts[:message] ] unless email =~ opts[:with]
    else
      return [ opts[:message] ] unless self.validate_local_part_syntax(local) and self.validate_domain_part_syntax(domain)
    end

    if opts[:check_mx] and !self.validate_email_domain(email)
      return [ opts[:mx_message] ]
    end

    return nil    # represents no validation errors
end

.validate_local_part_syntax(local) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/validates_email_format_of.rb', line 78

def self.validate_local_part_syntax(local)
  in_quoted_pair = false
  in_quoted_string = false

  (0..local.length-1).each do |i|
    ord = local[i].ord

    # accept anything if it's got a backslash before it
    if in_quoted_pair
      in_quoted_pair = false
      next
    end

    # backslash signifies the start of a quoted pair
    if ord == 92 and i < local.length - 1
      return false if not in_quoted_string # must be in quoted string per http://www.rfc-editor.org/errata_search.php?rfc=3696
      in_quoted_pair = true
      next
    end

    # double quote delimits quoted strings
    if ord == 34
      in_quoted_string = !in_quoted_string
      next
    end

    next if local[i,1] =~ /[a-z0-9]/i
    next if local[i,1] =~ LocalPartSpecialChars

    # period must be followed by something
    if ord == 46
      return false if i == 0 or i == local.length - 1 # can't be first or last char
      next unless local[i+1].ord == 46 # can't be followed by a period
    end

    return false
  end

  return false if in_quoted_string # unbalanced quotes

  return true
end