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 =
"does not appear to be valid"
DEFAULT_MX_MESSAGE =
"is not routable"
ERROR_MESSAGE_I18N_KEY =
:invalid_email_address
ERROR_MX_MESSAGE_I18N_KEY =
:email_address_not_routable
VERSION =
'1.6.3'

Class Method Summary collapse

Class Method Details

.default_messageObject


27
28
29
# File 'lib/validates_email_format_of.rb', line 27

def self.default_message
  defined?(I18n) ? I18n.t(ERROR_MESSAGE_I18N_KEY, :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


126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/validates_email_format_of.rb', line 126

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

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)

  • generate_message Return the I18n key of the error message instead of the error message itself (default is false)


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
76
77
78
79
80
# File 'lib/validates_email_format_of.rb', line 42

def self.validate_email_format(email, options={})
    default_options = { :message => options[:generate_message] ? ERROR_MESSAGE_I18N_KEY : default_message,
                        :check_mx => false,
                        :mx_message => options[:generate_message] ? ERROR_MX_MESSAGE_I18N_KEY : (defined?(I18n) ? I18n.t(ERROR_MX_MESSAGE_I18N_KEY, :scope => [:activemodel, :errors, :messages], :default => DEFAULT_MX_MESSAGE) : DEFAULT_MX_MESSAGE),
                        :domain_length => 255,
                        :local_length => 64,
                        :generate_message => false
                        }
    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


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
120
121
122
123
124
# File 'lib/validates_email_format_of.rb', line 83

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