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
- ATEXT_SYMBOLS =
/[!\#$%&'*\-\/=?+\^_`{|}~]/- ATEXT =
Characters that are allowed in to appear in the local part unquoted www.rfc-editor.org/rfc/rfc5322#section-3.4.1
An addr-spec is a specific Internet identifier that contains a locally interpreted string followed by the at-sign character (“@”, ASCII value 64) followed by an Internet domain. The locally interpreted string is either a quoted-string or a dot-atom. If the string can be represented as a dot-atom (that is, it contains no characters other than atext characters or “.” surrounded by atext characters), then the dot-atom form SHOULD be used and the quoted- string form SHOULD NOT be used. Comments and folding white space SHOULD NOT be used around the “@” in the addr-spec.
dot-atom-text = 1*atext *("." 1*atext) dot-atom = [CFWS] dot-atom-text [CFWS] /\A[A-Z0-9#{ATEXT_SYMBOLS}]\z/i- CTEXT =
Characters that are allowed to appear unquoted in comments www.rfc-editor.org/rfc/rfc5322#section-3.2.2
ctext = %d33-39 / %d42-91 / %d93-126 ccontent = ctext / quoted-pair / comment comment = “(” *([FWS] ccontent) [FWS] “)” CFWS = (1*( comment) [FWS]) / FWS
/\A[#{Regexp.escape([33..39, 42..91, 93..126].map { |ascii_range| ascii_range.map(&:chr) }.flatten.join)}\s]/i- QTEXT =
www.rfc-editor.org/rfc/rfc5322#section-3.2.4
Strings of characters that include characters other than those allowed in atoms can be represented in a quoted string format, where the characters are surrounded by quote (DQUOTE, ASCII value 34) characters.
qtext = %d33 / ; Printable US-ASCII
%d35-91 / ; characters not including %d93-126 / ; "\" or the quote character obs-qtextqcontent = qtext / quoted-pair quoted-string = [CFWS]
DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS] /\A[#{Regexp.escape([33..33, 35..91, 93..126].map { |ascii_range| ascii_range.map(&:chr) }.flatten.join)}\s]/i- IP_OCTET =
/\A[0-9]+\Z/- DOMAIN_PART_LABEL =
From datatracker.ietf.org/doc/html/rfc1035#section-2.3.1
> The labels must follow the rules for ARPANET host names. They must > start with a letter, end with a letter or digit, and have as interior > characters only letters, digits, and hyphen. There are also some > restrictions on the length. Labels must be 63 characters or less.
<label> | <subdomain> “.” <label> <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ] <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str> <let-dig-hyp> ::= <let-dig> | “-” <let-dig> ::= <letter> | <digit>
/\A[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9]?\Z/- DOMAIN_PART_TLD =
From tools.ietf.org/id/draft-liman-tld-names-00.html#rfc.section.2
> A TLD label MUST be at least two characters long and MAY be as long as 63 characters - > not counting any leading or trailing periods (.). It MUST consist of only ASCII characters > from the groups “letters” (A-Z), “digits” (0-9) and “hyphen” (-), and it MUST start with an > ASCII “letter”, and it MUST NOT end with a “hyphen”. Upper and lower case MAY be mixed at random, > since DNS lookups are case-insensitive.
tldlabel = ALPHA *61(ldh) ld ldh = ld / “-” ld = ALPHA / DIGIT ALPHA = %x41-5A / %x61-7A ; A-Z / a-z DIGIT = %x30-39 ; 0-9
/\A[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9]\Z/- 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.7.0"
Class Method Summary collapse
- .default_message ⇒ Object
- .load_i18n_locales ⇒ Object
- .validate_domain_part_syntax(domain) ⇒ Object
- .validate_email_domain(email, check_mx_timeout: 3) ⇒ Object
-
.validate_email_format(email, options = {}) ⇒ Object
Validates whether the specified value is a valid email address.
- .validate_local_part_syntax(local) ⇒ Object
Class Method Details
.default_message ⇒ Object
102 103 104 |
# File 'lib/validates_email_format_of.rb', line 102 def self. defined?(I18n) ? I18n.t(ERROR_MESSAGE_I18N_KEY, scope: [:activemodel, :errors, :messages], default: DEFAULT_MESSAGE) : DEFAULT_MESSAGE end |
.load_i18n_locales ⇒ Object
4 5 6 7 |
# File 'lib/validates_email_format_of.rb', line 4 def self.load_i18n_locales require "i18n" I18n.load_path += Dir.glob(File.(File.join(File.dirname(__FILE__), "..", "config", "locales", "*.yml"))) end |
.validate_domain_part_syntax(domain) ⇒ Object
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/validates_email_format_of.rb', line 225 def self.validate_domain_part_syntax(domain) parts = domain.downcase.split(".", -1) return false if parts.length <= 1 # Only one domain part # ipv4 return true if parts.length == 4 && parts.all? { |part| part =~ IP_OCTET && part.to_i.between?(0, 255) } # From https://datatracker.ietf.org/doc/html/rfc3696#section-2 this is the recommended, pragmatic way to validate a domain name: # # > It is likely that the better strategy has now become to make the "at least one period" test, # > to verify LDH conformance (including verification that the apparent TLD name is not all-numeric), # > and then to use the DNS to determine domain name validity, rather than trying to maintain # > a local list of valid TLD names. # # We do a little bit more but not too much and validate the tokens but do not check against a list of valid TLDs. parts.each do |part| return false if part.nil? || part.empty? return false if part.length > 63 return false unless DOMAIN_PART_LABEL.match?(part) end return false unless DOMAIN_PART_TLD.match?(parts[-1]) true end |
.validate_email_domain(email, check_mx_timeout: 3) ⇒ Object
88 89 90 91 92 93 94 95 |
# File 'lib/validates_email_format_of.rb', line 88 def self.validate_email_domain(email, check_mx_timeout: 3) domain = email.to_s.downcase.match(/@(.+)/)[1] Resolv::DNS.open do |dns| dns.timeouts = check_mx_timeout @mx = dns.getresources(domain, Resolv::DNS::Resource::IN::MX) + dns.getresources(domain, Resolv::DNS::Resource::IN::A) end @mx.size > 0 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) -
check_mx_timeout- Timeout in seconds for checking MX records before a ‘ResolvTimeout` is raised (default is 3) -
mx_message- A custom error message when an MX record validation fails (default is: “is not routable.”) -
withThe regex to use for validating the format of the email address (deprecated) -
local_lengthMaximum number of characters allowed in the local part (default is 64) -
domain_lengthMaximum number of characters allowed in the domain part (default is 255) -
generate_messageReturn the I18n key of the error message instead of the error message itself (default is false)
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/validates_email_format_of.rb', line 118 def self.validate_email_format(email, = {}) = {message: [:generate_message] ? ERROR_MESSAGE_I18N_KEY : , check_mx: false, check_mx_timeout: 3, mx_message: if [:generate_message] ERROR_MX_MESSAGE_I18N_KEY else (defined?(I18n) ? I18n.t(ERROR_MX_MESSAGE_I18N_KEY, scope: [:activemodel, :errors, :messages], default: DEFAULT_MX_MESSAGE) : DEFAULT_MX_MESSAGE) end, domain_length: 255, local_length: 64, generate_message: false} opts = .merge() { |key, old, new| old } # merge the default options into the specified options, retaining all specified options begin domain, local = email.reverse.split("@", 2) rescue return [opts[:message]] end # need local and domain parts return [opts[:message]] unless local && !local.empty? && domain && !domain.empty? # check lengths return [opts[:message]] unless domain.length <= opts[:domain_length] && 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&.match?(opts[:with]) else return [opts[:message]] unless validate_local_part_syntax(local) && validate_domain_part_syntax(domain) end if opts[:check_mx] && !validate_email_domain(email, check_mx_timeout: opts[:check_mx_timeout]) return [opts[:mx_message]] end nil # represents no validation errors end |
.validate_local_part_syntax(local) ⇒ Object
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/validates_email_format_of.rb', line 160 def self.validate_local_part_syntax(local) in_quoted_pair = false in_quoted_string = false comment_depth = 0 (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 if in_quoted_string next if QTEXT.match?(local[i]) end # opening paren to show we are going into a comment (CFWS) if ord == 40 comment_depth += 1 next end # closing paren if ord == 41 comment_depth -= 1 return false if comment_depth < 0 next end # backslash signifies the start of a quoted pair if ord == 92 && i < local.length - 1 return false if !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 if comment_depth > 0 next if CTEXT.match?(local[i]) elsif ATEXT.match?(local[i, 1]) next end # period must be followed by something if ord == 46 return false if i == 0 || 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 false unless comment_depth.zero? # unbalanced comment parens true end |