validates_email_format_of 1.7.2 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6220bc1f3cb06d9af63e0bcf8ccfcc6e1dc795a59a5c08cd817ac921a057585b
4
- data.tar.gz: 6c0f414549308510ad6434c71b3a1d0cabd88c4fc18cb3a3fb02135b862f3f5b
3
+ metadata.gz: 2329a74ad3d6ce656ab392715c9c2cbda4c4af6fbffcce69f5e98afd8018f0ae
4
+ data.tar.gz: 4ad12aef8277878f61c32a82b231d58251fe2e674ed262a021814075e9df2b17
5
5
  SHA512:
6
- metadata.gz: 91e1f3eb18440c304f144d81b346134d9c6d7bc747e43fe464edeebb9e3a02e972e7fa4b4283d7b9eed6e4556f37a578d11bb4a9bb0328e93f5f2f2658625a30
7
- data.tar.gz: 2e3c8cc64f8d860c0fb1e7771197942901a4fae28b166376b613bbef6262275003bbad62b23e244a3a9887ecb99c5f1dad7bc3f3b973150beaa5774782baed2d
6
+ metadata.gz: b454131a1c1f7ded070a65144ccbb9a27add0efa96ce681b578d9e7497f39234b1985863327a51e2179752dc06b9586978c762accaf5daea7e600df34cf11599
7
+ data.tar.gz: 76badfe2f7d9b8b75142c2f9b369f485d5b0cd6d3db660206e120bd9eb426f1ed8bd089e58bcb9651c64e5e47ea589eef8c040021da8adb3635957e1f1e3fd27
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "monthly"
@@ -1,10 +1,8 @@
1
1
  name: CI
2
2
 
3
3
  on:
4
- push:
5
- branches: [master]
6
- pull_request:
7
- branches: [master]
4
+ - push
5
+ - pull_request
8
6
 
9
7
  permissions:
10
8
  contents: read
@@ -17,38 +15,70 @@ jobs:
17
15
 
18
16
  strategy:
19
17
  matrix:
20
- ruby: ["2.6", "2.7", "3.0", "3.1"]
21
- gemfile: ["4.2", "5.0", "5.1", "5.2", "6.0", "6.1", "7.0"]
18
+ ruby: ["2.6", "2.7", "3.0", "3.1", "3.2", "3.3"]
19
+ gemfile: ["4.2", "5.0", "5.1", "5.2", "6.0", "6.1", "7.0", "7.1"]
20
+
22
21
  exclude:
23
22
  - gemfile: "4.2"
24
23
  ruby: "3.0"
25
24
  - gemfile: "4.2"
26
25
  ruby: "3.1"
26
+ - gemfile: "4.2"
27
+ ruby: "3.2"
28
+ - gemfile: "4.2"
29
+ ruby: "3.3"
27
30
  - gemfile: "5.0"
28
31
  ruby: "3.0"
29
32
  - gemfile: "5.0"
30
33
  ruby: "3.1"
34
+ - gemfile: "5.0"
35
+ ruby: "3.2"
36
+ - gemfile: "5.0"
37
+ ruby: "3.3"
31
38
  - gemfile: "5.1"
32
39
  ruby: "3.0"
33
40
  - gemfile: "5.1"
34
41
  ruby: "3.1"
42
+ - gemfile: "5.1"
43
+ ruby: "3.2"
44
+ - gemfile: "5.1"
45
+ ruby: "3.3"
35
46
  - gemfile: "5.2"
36
47
  ruby: "3.0"
37
48
  - gemfile: "5.2"
38
49
  ruby: "3.1"
50
+ - gemfile: "5.2"
51
+ ruby: "3.2"
52
+ - gemfile: "5.2"
53
+ ruby: "3.3"
54
+ - gemfile: "6.0"
55
+ ruby: "3.2"
56
+ - gemfile: "6.0"
57
+ ruby: "3.3"
58
+ - gemfile: "6.1"
59
+ ruby: "3.2"
60
+ - gemfile: "6.1"
61
+ ruby: "3.3"
39
62
  - gemfile: "7.0"
40
63
  ruby: "2.5"
41
64
  - gemfile: "7.0"
42
65
  ruby: "2.6"
43
66
  - gemfile: "7.0"
44
67
  ruby: "2.7"
68
+ - gemfile: "7.1"
69
+ ruby: "2.5"
70
+ - gemfile: "7.1"
71
+ ruby: "2.6"
72
+ - gemfile: "7.1"
73
+ ruby: "2.7"
74
+
45
75
 
46
76
  env:
47
77
  BUNDLE_GEMFILE: gemfiles/rails_${{ matrix.gemfile }}.gemfile
48
78
  RAILS_ENV: test
49
79
 
50
80
  steps:
51
- - uses: actions/checkout@v3
81
+ - uses: actions/checkout@v4
52
82
 
53
83
  - name: "Install Ruby ${{ matrix.ruby }}"
54
84
  # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
@@ -65,3 +95,4 @@ jobs:
65
95
 
66
96
  - name: Run standard.rb
67
97
  run: bundle exec rake standard
98
+ if: ${{ ! startsWith(matrix.ruby, '2.') }}
data/Appraisals CHANGED
@@ -1,4 +1,8 @@
1
1
  # run `bundle exec appraisal install` after making changes here
2
+ appraise "rails-7.1" do
3
+ gem "rails", "~> 7.1"
4
+ end
5
+
2
6
  appraise "rails-7.0" do
3
7
  gem "rails", "~> 7.0"
4
8
  end
data/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ [Unreleased]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.8.0...master
6
+
7
+ ## [1.8.0]
8
+
9
+ * Add Internationalized Domain Name support - https://github.com/validates-email-format-of/validates_email_format_of/pull/103 - thanks https://github.com/sbilharz !
10
+ * Add Turkish locale - https://github.com/validates-email-format-of/validates_email_format_of/pull/101 - thanks https://github.com/@krmbzds !
11
+ * Added Indonesian locale - https://github.com/validates-email-format-of/validates_email_format_of/commit/129ebfc3a3b432b4df0334bcbdd74b1d17d765e0 - thanks https://github.com/khoerodin !
12
+ * Fix inconsistent `generate_messages` behaviour - https://github.com/validates-email-format-of/validates_email_format_of/pull/105
13
+ * ⚠️ Deprecate `:with` option - https://github.com/validates-email-format-of/validates_email_format_of/issues/42
14
+ * Require i18n >= 0.8.0 in modern Ruby versions - https://github.com/advisories/GHSA-34hf-g744-jw64
15
+
5
16
  [Unreleased]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.7.2...master
6
17
 
7
18
  ## [1.7.2]
data/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # validates_email_format_of
2
+
3
+ [![Build Status](https://github.com/validates-email-format-of/validates_email_format_of/actions/workflows/ci.yml/badge.svg)]( https://github.com/validates-email-format-of/validates_email_format_of/actions/workflows/ci.yml?query=branch%3Amaster)
4
+
5
+ A Ruby gem to validate email addresses against RFC 2822 and RFC 5322.
6
+
7
+ ## Why this email validator?
8
+
9
+ This gem is the O.G. email validation gem for Rails. It was started back in 2006.
10
+
11
+ Why use this validator? Instead of trying to validate email addresses with one giant regular expression, this library parses addresses character by character. This lets us handle weird cases like [nested comments](https://www.rfc-editor.org/rfc/rfc5322#appendix-A.5). Gross but technically allowed.
12
+
13
+ In reality, most email validating scripts will get you where you need to go. This library just aims to go all the way.
14
+
15
+ ## Installation
16
+
17
+ Add the gem to your Gemfile with:
18
+
19
+ ```sh
20
+ gem 'validates_email_format_of'
21
+ ```
22
+
23
+ ### Usage in a Rails app
24
+
25
+ ```ruby
26
+ class Person < ActiveRecord::Base
27
+ validates :email, :email_format => { :message => "is not looking good" }
28
+
29
+ # OR
30
+
31
+ validates_email_format_of :email, :message => "is not looking good"
32
+ end
33
+ ```
34
+
35
+ You can use the included `rspec` matcher as well:
36
+
37
+ ```ruby
38
+ require "validates_email_format_of/rspec_matcher"
39
+
40
+ describe Person do
41
+ it { should validate_email_format_of(:email).with_message("is not looking good") }
42
+ end
43
+ ```
44
+
45
+ ### Usage without Rails
46
+
47
+ ```ruby
48
+ # Optional, if you want error messages to be in your language
49
+ ValidatesEmailFormatOf::load_i18n_locales
50
+ I18n.locale = :pl
51
+
52
+ ValidatesEmailFormatOf::validate_email_format("example@mydomain.com") # => nil
53
+ ValidatesEmailFormatOf::validate_email_format("invalid@") # => ["does not appear to be a valid email address"]
54
+ ```
55
+
56
+ ## Options
57
+
58
+ | Option | Type | Description |
59
+ | --- | --- | --- |
60
+ | `:message` | String | A custom error message when the email format is invalid (default is: "does not appear to be a valid email address") |
61
+ | `:check_mx` | Boolean | Check domain for a valid MX record (default is false) |
62
+ | `:check_mx_timeout` | Integer | Timeout in seconds for checking MX records before a `ResolvTimeout` is raised (default is 3). |
63
+ | `:idn` | Boolean | Allowed internationalized domain names like `test@exämple.com` and `test@пример.рф`. Otherwise only domains that have already been converted to [Punycode](https://en.wikipedia.org/wiki/Punycode) are supported. (default is true) |
64
+ | `:mx_message` | String | A custom error message when the domain does not match a valid MX record (default is: "is not routable"). Ignored unless :check_mx option is true. |
65
+ | `:local_length` |Integer | Maximum number of characters allowed in the local part (everything before the '@') (default is 64) |
66
+ | `:domain_length` | Integer | Maximum number of characters allowed in the domain part (everything after the '@') (default is 255) |
67
+ | `:generate_message` | Boolean | Return the I18n key of the error message instead of the error message itself (default is false) |
68
+
69
+ The standard ActiveModel validation options (`:on`, `:if`, `:unless`, `:allow_nil`, `:allow_blank`, etc...) all work as well when using the gem as part of a Rails application.
70
+ ## Testing
71
+
72
+ You can see our [current Ruby and Rails test matrix here](.github/workflows/ci.yml).
73
+
74
+ To execute the unit tests against [all the Rails versions we support run](gemfiles/) <tt>bundle exec appraisal rspec</tt> or run against an individual version with <tt>bundle exec appraisal rails-6.0 rspec</tt>.
75
+ ## Contributing
76
+
77
+ If you think we're letting some rules about valid email formats slip through the cracks, don't just update the parser. Instead, add a failing test and demonstrate that the described email address should be treated differently. A link to an appropriate RFC is the best way to do this. Then change the gem code to make the test pass.
78
+
79
+ ```ruby
80
+ describe "i_think_this_is_not_a_v@lid_email_addre.ss" do
81
+ # According to http://..., this email address IS NOT valid.
82
+ it { should have_errors_on_email.because("does not appear to be valid") }
83
+ end
84
+
85
+ describe "i_think_this_is_a_v@lid_email_addre.ss" do
86
+ # According to http://..., this email address IS valid.
87
+ it { should_not have_errors_on_email }
88
+ end
89
+ ```
90
+
91
+ Yes, our Rspec syntax is that simple!
92
+
93
+ ## Homepage
94
+
95
+ * https://github.com/validates-email-format-of/validates_email_format_of
96
+
97
+ ## Credits
98
+
99
+ Written by Alex Dunae (dunae.ca), 2006-22.
100
+
101
+ Many thanks to the plugin's recent contributors: https://github.com/alexdunae/validates_email_format_of/contributors
102
+
103
+ Thanks to Francis Hwang (http://fhwang.net/) at Diversion Media for creating the 1.1 update.
104
+
105
+ Thanks to Travis Sinnott for creating the 1.3 update.
106
+
107
+ Thanks to Denis Ahearn at Riverock Technologies (http://www.riverocktech.com/) for creating the 1.4 update.
108
+
109
+ Thanks to George Anderson (http://github.com/george) and 'history' (http://github.com/history) for creating the 1.4.1 update.
110
+
111
+ Thanks to Isaac Betesh (https://github.com/betesh) for converting tests to Rspec and refactoring for version 1.6.0.
@@ -2,7 +2,7 @@ de:
2
2
  activemodel: &errors
3
3
  errors:
4
4
  messages:
5
- invalid_email_address: 'ist offensichtlich keine gültige E-Mail-Adresse'
5
+ invalid_email_address: 'ist offensichtlich keine gültige EMail-Adresse'
6
6
  email_address_not_routable: 'kann nicht erreicht werden'
7
7
  activerecord:
8
8
  <<: *errors
@@ -2,7 +2,7 @@ en:
2
2
  activemodel: &errors
3
3
  errors:
4
4
  messages:
5
- invalid_email_address: 'does not appear to be a valid e-mail address'
5
+ invalid_email_address: 'does not appear to be a valid email address'
6
6
  email_address_not_routable: 'is not routable'
7
7
  activerecord:
8
8
  <<: *errors
@@ -0,0 +1,8 @@
1
+ id:
2
+ activemodel: &errors
3
+ errors:
4
+ messages:
5
+ invalid_email_address: 'tampaknya bukan alamat email yang valid'
6
+ email_address_not_routable: 'tidak dapat dirutekan'
7
+ activerecord:
8
+ <<: *errors
@@ -2,7 +2,7 @@ it:
2
2
  activemodel: &errors
3
3
  errors:
4
4
  messages:
5
- invalid_email_address: 'non sembra un indirizzo e-mail valido'
5
+ invalid_email_address: 'non sembra un indirizzo email valido'
6
6
  email_address_not_routable: 'dominio non raggiungibile'
7
7
  activerecord:
8
8
  <<: *errors
@@ -2,7 +2,7 @@ pl:
2
2
  activemodel: &errors
3
3
  errors:
4
4
  messages:
5
- invalid_email_address: 'nieprawidłowy adres e-mail'
5
+ invalid_email_address: 'nieprawidłowy adres email'
6
6
  email_address_not_routable: 'jest nieosiągalny'
7
7
  activerecord:
8
8
  <<: *errors
@@ -2,7 +2,7 @@ pt-BR:
2
2
  activemodel: &errors
3
3
  errors:
4
4
  messages:
5
- invalid_email_address: 'não parece ser um endereço de e-mail válido'
5
+ invalid_email_address: 'não parece ser um endereço de email válido'
6
6
  email_address_not_routable: 'não é acessível'
7
7
  activerecord:
8
8
  <<: *errors
@@ -2,7 +2,7 @@ pt:
2
2
  activemodel: &errors
3
3
  errors:
4
4
  messages:
5
- invalid_email_address: 'não parece ser um endereço de e-mail válido'
5
+ invalid_email_address: 'não parece ser um endereço de email válido'
6
6
  email_address_not_routable: 'não é acessível'
7
7
  activerecord:
8
8
  <<: *errors
@@ -0,0 +1,8 @@
1
+ tr:
2
+ activemodel: &errors
3
+ errors:
4
+ messages:
5
+ invalid_email_address: 'geçerli bir eposta adresi değil'
6
+ email_address_not_routable: 'ulaşılabilir değil'
7
+ activerecord:
8
+ <<: *errors
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "http://rubygems.org"
4
+
5
+ gem "rails", "~> 7.1"
6
+
7
+ gemspec path: "../"
@@ -9,7 +9,7 @@ module ActiveModel
9
9
  module Validations
10
10
  class EmailFormatValidator < EachValidator
11
11
  def validate_each(record, attribute, value)
12
- (ValidatesEmailFormatOf.validate_email_format(value, options.merge(generate_message: true)) || []).each do |error|
12
+ (ValidatesEmailFormatOf.validate_email_format(value, options) || []).each do |error|
13
13
  record.errors.add(attribute, error)
14
14
  end
15
15
  end
@@ -1,3 +1,3 @@
1
1
  module ValidatesEmailFormatOf
2
- VERSION = "1.7.2"
2
+ VERSION = "1.8.0"
3
3
  end
@@ -1,4 +1,5 @@
1
1
  require "validates_email_format_of/version"
2
+ require "simpleidn"
2
3
 
3
4
  module ValidatesEmailFormatOf
4
5
  def self.load_i18n_locales
@@ -77,7 +78,7 @@ module ValidatesEmailFormatOf
77
78
  # > restriction on the first character is relaxed to allow either a
78
79
  # > letter or a digit. Host software MUST support this more liberal
79
80
  # > syntax.
80
- DOMAIN_PART_LABEL = /\A[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]?\Z/
81
+ DOMAIN_PART_LABEL = /\A[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9]?\Z/
81
82
 
82
83
  # From https://tools.ietf.org/id/draft-liman-tld-names-00.html#rfc.section.2
83
84
  #
@@ -92,10 +93,12 @@ module ValidatesEmailFormatOf
92
93
  # ld = ALPHA / DIGIT
93
94
  # ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
94
95
  # DIGIT = %x30-39 ; 0-9
95
- DOMAIN_PART_TLD = /\A[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9]\Z/
96
+ DOMAIN_PART_TLD = /\A[A-Za-z][A-Za-z0-9-]*[A-Za-z0-9]\Z/
96
97
 
97
- def self.validate_email_domain(email, check_mx_timeout: 3)
98
+ def self.validate_email_domain(email, idn: true, check_mx_timeout: 3)
98
99
  domain = email.to_s.downcase.match(/@(.+)/)[1]
100
+ domain = SimpleIDN.to_ascii(domain) if idn
101
+
99
102
  Resolv::DNS.open do |dns|
100
103
  dns.timeouts = check_mx_timeout
101
104
  @mx = dns.getresources(domain, Resolv::DNS::Resource::IN::MX) + dns.getresources(domain, Resolv::DNS::Resource::IN::A)
@@ -119,8 +122,8 @@ module ValidatesEmailFormatOf
119
122
  # * <tt>message</tt> - A custom error message (default is: "does not appear to be valid")
120
123
  # * <tt>check_mx</tt> - Check for MX records (default is false)
121
124
  # * <tt>check_mx_timeout</tt> - Timeout in seconds for checking MX records before a `ResolvTimeout` is raised (default is 3)
125
+ # * <tt>idn</tt> - Enable or disable Internationalized Domain Names (default is true)
122
126
  # * <tt>mx_message</tt> - A custom error message when an MX record validation fails (default is: "is not routable.")
123
- # * <tt>with</tt> The regex to use for validating the format of the email address (deprecated)
124
127
  # * <tt>local_length</tt> Maximum number of characters allowed in the local part (default is 64)
125
128
  # * <tt>domain_length</tt> Maximum number of characters allowed in the domain part (default is 255)
126
129
  # * <tt>generate_message</tt> Return the I18n key of the error message instead of the error message itself (default is false)
@@ -128,6 +131,7 @@ module ValidatesEmailFormatOf
128
131
  default_options = {message: options[:generate_message] ? ERROR_MESSAGE_I18N_KEY : default_message,
129
132
  check_mx: false,
130
133
  check_mx_timeout: 3,
134
+ idn: true,
131
135
  mx_message: if options[:generate_message]
132
136
  ERROR_MX_MESSAGE_I18N_KEY
133
137
  else
@@ -154,12 +158,13 @@ module ValidatesEmailFormatOf
154
158
  domain.reverse!
155
159
 
156
160
  if opts.has_key?(:with) # holdover from versions <= 1.4.7
161
+ deprecation_warn(":with option is deprecated and will be removed in the next version")
157
162
  return [opts[:message]] unless email&.match?(opts[:with])
158
163
  else
159
- return [opts[:message]] unless validate_local_part_syntax(local) && validate_domain_part_syntax(domain)
164
+ return [opts[:message]] unless validate_local_part_syntax(local) && validate_domain_part_syntax(domain, idn: opts[:idn])
160
165
  end
161
166
 
162
- if opts[:check_mx] && !validate_email_domain(email, check_mx_timeout: opts[:check_mx_timeout])
167
+ if opts[:check_mx] && !validate_email_domain(email, check_mx_timeout: opts[:check_mx_timeout], idn: opts[:idn])
163
168
  return [opts[:mx_message]]
164
169
  end
165
170
 
@@ -171,6 +176,19 @@ module ValidatesEmailFormatOf
171
176
  in_quoted_string = false
172
177
  comment_depth = 0
173
178
 
179
+ # The local part is made up of dot-atom and quoted-string joined together by "." characters
180
+ #
181
+ # https://www.rfc-editor.org/rfc/rfc5322#section-3.4.1
182
+ # > local-part = dot-atom / quoted-string / obs-local-part
183
+ #
184
+ # https://www.rfc-editor.org/rfc/rfc5322#section-3.2.3
185
+ # Both atom and dot-atom are interpreted as a single unit, comprising
186
+ # > the string of characters that make it up. Semantically, the optional
187
+ # > comments and FWS surrounding the rest of the characters are not part
188
+ # > of the atom; the atom is only the run of atext characters in an atom,
189
+ # > or the atext and "." characters in a dot-atom.
190
+ joining_atoms = true
191
+
174
192
  (0..local.length - 1).each do |i|
175
193
  ord = local[i].ord
176
194
 
@@ -180,6 +198,32 @@ module ValidatesEmailFormatOf
180
198
  next
181
199
  end
182
200
 
201
+ # double quote delimits quoted strings
202
+ if ord == 34
203
+ if in_quoted_string # leaving the quoted string
204
+ in_quoted_string = false
205
+ next
206
+ elsif joining_atoms # are we allowed to enter a quoted string?
207
+ in_quoted_string = true
208
+ joining_atoms = false
209
+ next
210
+ else
211
+ return false
212
+ end
213
+ end
214
+
215
+ # period indicates we want to join atoms, e.g. `aaa.bbb."ccc"@example.com
216
+ if ord == 46
217
+ return false if i.zero?
218
+ return false if joining_atoms
219
+ joining_atoms = true
220
+ next
221
+ end
222
+
223
+ joining_atoms = false
224
+
225
+ # quoted string logic must come before comment processing since a quoted string
226
+ # may contain parens, e.g. `"name(a)"@example.com`
183
227
  if in_quoted_string
184
228
  next if QTEXT.match?(local[i])
185
229
  end
@@ -198,40 +242,34 @@ module ValidatesEmailFormatOf
198
242
  end
199
243
 
200
244
  # backslash signifies the start of a quoted pair
201
- if ord == 92 && i < local.length - 1
202
- return false if !in_quoted_string # must be in quoted string per http://www.rfc-editor.org/errata_search.php?rfc=3696
245
+ if ord == 92
246
+ # https://www.rfc-editor.org/rfc/rfc5322#section-3.2.1
247
+ # > The only places in this specification where quoted-pair currently appears are
248
+ # > ccontent, qcontent, and in obs-dtext in section 4.
249
+ return false unless in_quoted_string || comment_depth > 0
203
250
  in_quoted_pair = true
204
251
  next
205
252
  end
206
253
 
207
- # double quote delimits quoted strings
208
- if ord == 34
209
- in_quoted_string = !in_quoted_string
210
- next
211
- end
212
-
213
254
  if comment_depth > 0
214
255
  next if CTEXT.match?(local[i])
215
256
  elsif ATEXT.match?(local[i, 1])
216
257
  next
217
258
  end
218
259
 
219
- # period must be followed by something
220
- if ord == 46
221
- return false if i == 0 || i == local.length - 1 # can't be first or last char
222
- next unless local[i + 1].ord == 46 # can't be followed by a period
223
- end
224
-
225
260
  return false
226
261
  end
227
262
 
263
+ return false if in_quoted_pair # unbalanced quoted pair
228
264
  return false if in_quoted_string # unbalanced quotes
229
265
  return false unless comment_depth.zero? # unbalanced comment parens
266
+ return false if joining_atoms # the last char we encountered was a period
230
267
 
231
268
  true
232
269
  end
233
270
 
234
- def self.validate_domain_part_syntax(domain)
271
+ def self.validate_domain_part_syntax(domain, idn: true)
272
+ domain = SimpleIDN.to_ascii(domain) if idn
235
273
  parts = domain.downcase.split(".", -1)
236
274
 
237
275
  return false if parts.length <= 1 # Only one domain part
@@ -256,6 +294,14 @@ module ValidatesEmailFormatOf
256
294
  return false unless DOMAIN_PART_TLD.match?(parts[-1])
257
295
  true
258
296
  end
297
+
298
+ def self.deprecation_warn(msg)
299
+ if defined?(ActiveSupport::Deprecation)
300
+ ActiveSupport::Deprecation.warn(msg)
301
+ else
302
+ warn
303
+ end
304
+ end
259
305
  end
260
306
 
261
307
  require "validates_email_format_of/active_model" if defined?(::ActiveModel) && !(ActiveModel::VERSION::MAJOR < 2 || (ActiveModel::VERSION::MAJOR == 2 && ActiveModel::VERSION::MINOR < 1))
data/spec/spec_helper.rb CHANGED
@@ -4,7 +4,9 @@ require "pry"
4
4
  require "byebug"
5
5
 
6
6
  RSpec::Matchers.define :have_errors_on_email do
7
- match do |actual|
7
+ match do |user|
8
+ actual = user.errors.full_messages
9
+ expect(user.errors.added?(:email, ValidatesEmailFormatOf::ERROR_MESSAGE_I18N_KEY))
8
10
  expect(actual).not_to be_nil, "#{actual} should not be nil"
9
11
  expect(actual).not_to be_empty, "#{actual} should not be empty"
10
12
  expect(actual.size).to eq(@reasons.size), "#{actual} should have #{@reasons.size} elements"
@@ -19,7 +21,7 @@ RSpec::Matchers.define :have_errors_on_email do
19
21
  chain :and_because do |reason|
20
22
  (@reasons ||= []) << reason
21
23
  end
22
- match_when_negated do |actual|
23
- expect(actual).to(be_empty)
24
+ match_when_negated do |user|
25
+ expect(user.errors).to(be_empty)
24
26
  end
25
27
  end
@@ -22,7 +22,7 @@ describe ValidatesEmailFormatOf do
22
22
  ActiveModel::Name.new(self, nil, "User")
23
23
  end
24
24
  end
25
- user.new(example.example_group_instance.email).tap(&:valid?).errors.full_messages
25
+ user.new(example.example_group_instance.email).tap(&:valid?)
26
26
  end
27
27
  let(:options) { {} }
28
28
  let(:email) { |example| example.example_group.description }
@@ -58,13 +58,24 @@ describe ValidatesEmailFormatOf do
58
58
  "_somename@example.com",
59
59
  # apostrophes
60
60
  "test'test@example.com",
61
- # international domain names
61
+ # punycode domain names
62
62
  "test@xn--bcher-kva.ch",
63
63
  "test@example.xn--0zwm56d",
64
+
65
+ # IDN domains,
66
+ "test@exämple.com",
67
+ "test@пример.рф",
68
+ "test@почта.бел",
69
+
64
70
  "test@192.192.192.1",
65
71
  # Allow quoted characters. Valid according to http://www.rfc-editor.org/errata_search.php?rfc=3696
66
72
  '"Abc\@def"@example.com',
73
+ "\"quote\".dotatom.\"otherquote\"@example.com",
67
74
  '"Quote(Only".Chars@wier.de',
75
+ "\"much.more unusual\"@example.com",
76
+ "\"very.unusual.@.unusual.com\"@example.com",
77
+ '"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com',
78
+ '"()<>[]:,;@\"!#$%&*+-/=?^_`{}| ~ ? ^_`{}|~.a"@example.org',
68
79
  '"Fred\ Bloggs"@example.com',
69
80
  '"Joe.\\Blow"@example.com',
70
81
  # Balanced quoted characters
@@ -98,6 +109,7 @@ describe ValidatesEmailFormatOf do
98
109
  "invalid@example.com-",
99
110
  "invalid-example.com",
100
111
  "invalid@example.b#r.com",
112
+ "just\"not\"right@example.com",
101
113
  "invalid@example.c",
102
114
  "invali d@example.com",
103
115
  # TLD can not be only numeric
@@ -131,7 +143,7 @@ describe ValidatesEmailFormatOf do
131
143
  "(unbalancedcomment@example.com"
132
144
  ].each do |address|
133
145
  describe address do
134
- it { should have_errors_on_email.because("does not appear to be a valid e-mail address") }
146
+ it { should have_errors_on_email.because("does not appear to be a valid email address") }
135
147
  end
136
148
  end
137
149
 
@@ -141,7 +153,7 @@ describe ValidatesEmailFormatOf do
141
153
  it { should_not have_errors_on_email }
142
154
  end
143
155
  describe "#{"a" * (limit + 1)}@example.com" do
144
- it { should have_errors_on_email.because("does not appear to be a valid e-mail address") }
156
+ it { should have_errors_on_email.because("does not appear to be a valid email address") }
145
157
  end
146
158
  end
147
159
  describe "when using default" do
@@ -158,7 +170,7 @@ describe ValidatesEmailFormatOf do
158
170
  it { should_not have_errors_on_email }
159
171
  end
160
172
  describe "user@#{"a." * (limit / 2 + 1)}com" do
161
- it { should have_errors_on_email.because("does not appear to be a valid e-mail address") }
173
+ it { should have_errors_on_email.because("does not appear to be a valid email address") }
162
174
  end
163
175
  end
164
176
  describe "when using default" do
@@ -179,6 +191,26 @@ describe ValidatesEmailFormatOf do
179
191
  end
180
192
  end
181
193
 
194
+ describe "when idn support is disabled" do
195
+ before(:each) do
196
+ allow(SimpleIDN).to receive(:to_ascii).never
197
+ end
198
+ let(:options) { {idn: false} }
199
+ describe "test@exämple.com" do
200
+ it { should have_errors_on_email.because("does not appear to be a valid email address") }
201
+ end
202
+ end
203
+
204
+ describe "when idn support is enabled" do
205
+ before(:each) do
206
+ allow(SimpleIDN).to receive(:to_ascii).once.with("exämple.com").and_return("xn--exmple-cua.com")
207
+ end
208
+ let(:options) { {idn: true} }
209
+ describe "test@exämple.com" do
210
+ it { should_not have_errors_on_email }
211
+ end
212
+ end
213
+
182
214
  describe "mx record" do
183
215
  domain = "example.com"
184
216
  email = "valid@#{domain}"
@@ -216,6 +248,9 @@ describe ValidatesEmailFormatOf do
216
248
  let(:mx_record) { [] }
217
249
  describe email do
218
250
  it { should have_errors_on_email.because("is not routable") }
251
+ it "adds the i18n key" do
252
+ subject.errors.added?(:email, ValidatesEmailFormatOf::ERROR_MX_MESSAGE_I18N_KEY)
253
+ end
219
254
  end
220
255
  describe "with a custom error message" do
221
256
  let(:options) { {check_mx: true, mx_message: "There ain't no such domain!"} }
@@ -223,6 +258,7 @@ describe ValidatesEmailFormatOf do
223
258
  it { should have_errors_on_email.because("There ain't no such domain!") }
224
259
  end
225
260
  end
261
+
226
262
  describe "i18n" do
227
263
  before(:each) do
228
264
  allow(I18n.config).to receive(:locale).and_return(locale)
@@ -236,6 +272,7 @@ describe ValidatesEmailFormatOf do
236
272
  end
237
273
  end
238
274
  end
275
+
239
276
  describe "when not testing" do
240
277
  before(:each) { allow(Resolv::DNS).to receive(:open).never }
241
278
  describe "by default" do
@@ -258,13 +295,43 @@ describe ValidatesEmailFormatOf do
258
295
  end
259
296
  end
260
297
 
298
+ describe "mx record for internationalized domain" do
299
+ domain = "пример.рф"
300
+ email = "valid@#{domain}"
301
+
302
+ describe "when idn support is enabled" do
303
+ let(:dns) { double(Resolv::DNS) }
304
+ let(:options) { {check_mx: true, idn: true} }
305
+
306
+ before(:each) do
307
+ allow(Resolv::DNS).to receive(:open).and_yield(dns)
308
+ allow(dns).to receive(:"timeouts=").with(3).once
309
+ allow(dns).to receive(:getresources).with(SimpleIDN.to_ascii(domain), Resolv::DNS::Resource::IN::A).once.and_return([double])
310
+ allow(dns).to receive(:getresources).with(SimpleIDN.to_ascii(domain), Resolv::DNS::Resource::IN::MX).once.and_return([double])
311
+ end
312
+
313
+ describe email do
314
+ it { should_not have_errors_on_email }
315
+ end
316
+ end
317
+
318
+ describe "when idn support is disabled" do
319
+ let(:options) { {check_mx: true, idn: false} }
320
+
321
+ describe "test@пример.рф" do
322
+ let(:domain) { "exämple.com" }
323
+ it { should have_errors_on_email.because("does not appear to be a valid email address") }
324
+ end
325
+ end
326
+ end
327
+
261
328
  describe "custom regex" do
262
329
  let(:options) { {with: /[0-9]+@[0-9]+/} }
263
330
  describe "012345@789" do
264
331
  it { should_not have_errors_on_email }
265
332
  end
266
333
  describe "valid@example.com" do
267
- it { should have_errors_on_email.because("does not appear to be a valid e-mail address") }
334
+ it { should have_errors_on_email.because("does not appear to be a valid email address") }
268
335
  end
269
336
  end
270
337
 
@@ -275,7 +342,7 @@ describe ValidatesEmailFormatOf do
275
342
  describe "present locale" do
276
343
  let(:locale) { :pl }
277
344
  describe "invalid@exmaple." do
278
- it { should have_errors_on_email.because("nieprawidłowy adres e-mail") }
345
+ it { should have_errors_on_email.because("nieprawidłowy adres email") }
279
346
  end
280
347
  end
281
348
  end
@@ -320,7 +387,7 @@ describe ValidatesEmailFormatOf do
320
387
  ActiveModel::Name.new(self, nil, "User")
321
388
  end
322
389
  end
323
- user.new(example.example_group_instance.email).tap(&:valid?).errors.full_messages
390
+ user.new(example.example_group_instance.email).tap(&:valid?)
324
391
  end
325
392
 
326
393
  it_should_behave_like :all_specs
@@ -4,7 +4,7 @@ require "validates_email_format_of/version"
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "validates_email_format_of"
6
6
  s.version = ValidatesEmailFormatOf::VERSION
7
- s.summary = "Validate e-mail addresses against RFC 2822 and RFC 3696."
7
+ s.summary = "Validate email addresses against RFC 2822 and RFC 3696."
8
8
  s.description = s.summary
9
9
  s.authors = ["Alex Dunae", "Isaac Betesh"]
10
10
  s.email = ["code@dunae.ca", "iybetesh@gmail.com"]
@@ -16,9 +16,10 @@ Gem::Specification.new do |s|
16
16
  if RUBY_VERSION < "1.9.3"
17
17
  s.add_dependency "i18n", "< 0.7.0"
18
18
  else
19
- s.add_dependency "i18n"
19
+ s.add_dependency "i18n", ">= 0.8.0"
20
20
  end
21
21
 
22
+ s.add_dependency "simpleidn"
22
23
  s.add_development_dependency "activemodel"
23
24
  s.add_development_dependency "bundler"
24
25
  s.add_development_dependency "rspec"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: validates_email_format_of
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.2
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Dunae
@@ -9,10 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-08-08 00:00:00.000000000 Z
12
+ date: 2024-03-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: i18n
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: 0.8.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: 0.8.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: simpleidn
16
30
  requirement: !ruby/object:Gem::Requirement
17
31
  requirements:
18
32
  - - ">="
@@ -109,7 +123,7 @@ dependencies:
109
123
  - - ">="
110
124
  - !ruby/object:Gem::Version
111
125
  version: '0'
112
- description: Validate e-mail addresses against RFC 2822 and RFC 3696.
126
+ description: Validate email addresses against RFC 2822 and RFC 3696.
113
127
  email:
114
128
  - code@dunae.ca
115
129
  - iybetesh@gmail.com
@@ -117,6 +131,7 @@ executables: []
117
131
  extensions: []
118
132
  extra_rdoc_files: []
119
133
  files:
134
+ - ".github/dependabot.yml"
120
135
  - ".github/workflows/ci.yml"
121
136
  - ".gitignore"
122
137
  - ".rspec"
@@ -124,15 +139,17 @@ files:
124
139
  - CHANGELOG.md
125
140
  - Gemfile
126
141
  - MIT-LICENSE
127
- - README.rdoc
142
+ - README.md
128
143
  - config/locales/de.yml
129
144
  - config/locales/en.yml
130
145
  - config/locales/fr.yml
146
+ - config/locales/id.yml
131
147
  - config/locales/it.yml
132
148
  - config/locales/ja.yml
133
149
  - config/locales/pl.yml
134
150
  - config/locales/pt-BR.yml
135
151
  - config/locales/pt.yml
152
+ - config/locales/tr.yml
136
153
  - gemfiles/rails_4.2.gemfile
137
154
  - gemfiles/rails_5.0.gemfile
138
155
  - gemfiles/rails_5.1.gemfile
@@ -140,6 +157,7 @@ files:
140
157
  - gemfiles/rails_6.0.gemfile
141
158
  - gemfiles/rails_6.1.gemfile
142
159
  - gemfiles/rails_7.0.gemfile
160
+ - gemfiles/rails_7.1.gemfile
143
161
  - lib/validates_email_format_of.rb
144
162
  - lib/validates_email_format_of/active_model.rb
145
163
  - lib/validates_email_format_of/railtie.rb
@@ -169,8 +187,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
187
  - !ruby/object:Gem::Version
170
188
  version: '0'
171
189
  requirements: []
172
- rubygems_version: 3.1.6
190
+ rubygems_version: 3.4.21
173
191
  signing_key:
174
192
  specification_version: 4
175
- summary: Validate e-mail addresses against RFC 2822 and RFC 3696.
193
+ summary: Validate email addresses against RFC 2822 and RFC 3696.
176
194
  test_files: []
data/README.rdoc DELETED
@@ -1,100 +0,0 @@
1
- = validates_email_format_of Gem and Rails Plugin
2
-
3
- Validate e-mail addresses against RFC 2822 and RFC 3696.
4
-
5
- == Installation
6
-
7
- Installing as a gem:
8
-
9
- gem install validates_email_format_of
10
-
11
- Or in your Gemfile:
12
-
13
- gem 'validates_email_format_of'
14
-
15
- == Usage
16
-
17
- # Rails
18
- # I18n locales are loaded automatically.
19
- class Person < ActiveRecord::Base
20
- validates_email_format_of :email, :message => 'is not looking good'
21
- # OR
22
- validates :email, :email_format => { :message => 'is not looking good' }
23
- end
24
-
25
- # Now you can test your model using RSpec:
26
- require "validates_email_format_of/rspec_matcher"
27
- describe Person do
28
- it { should validate_email_format_of(:email).with_message('is not looking good') }
29
- end
30
-
31
- # If you're not using Rails (which really means, if you're not using ActiveModel::Validations)
32
- ValidatesEmailFormatOf::load_i18n_locales # Optional, if you want error messages to be in your language
33
- I18n.locale = :pl # If, for example, you want Polish error messages.
34
- ValidatesEmailFormatOf::validate_email_format("example@mydomain.com") # => nil
35
- ValidatesEmailFormatOf::validate_email_format("invalid_because_there_is_no_at_symbol") # => ["does not appear to be a valid e-mail address"]
36
-
37
- === Options
38
-
39
- :message
40
- String. A custom error message when the email format is invalid (default is: "does not appear to be a valid e-mail address")
41
- :check_mx
42
- Boolean. Check domain for a valid MX record (default is false)
43
- :check_mx_timeout
44
- Integer. Timeout in seconds for checking MX records before a `ResolvTimeout` is raised (default is 3).
45
- :mx_message
46
- String. A custom error message when the domain does not match a valid MX record (default is: "is not routable"). Ignored unless :check_mx option is true.
47
- :local_length
48
- Maximum number of characters allowed in the local part (everything before the '@') (default is 64)
49
- :domain_length
50
- Maximum number of characters allowed in the domain part (everything after the '@') (default is 255)
51
- :generate_message
52
- Boolean. Return the I18n key of the error message instead of the error message itself (default is false)
53
- :with
54
- Specify a custom Regex as the valid email format.
55
- :on, :if, :unless, :allow_nil, :allow_blank, :strict
56
- Standard ActiveModel validation options. These work in the ActiveModel/ActiveRecord/Rails syntax only.
57
- See http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validates for details.
58
-
59
- == Testing
60
-
61
- To execute the unit tests against [all the Rails versions we support run](gemfiles/) <tt>bundle exec appraisal rspec</tt> or run against an individual version with <tt>bundle exec appraisal rails-6.0 rspec</tt>.
62
-
63
- Tested in Ruby 1.8.7, 1.9.2, 1.9.3, 2.0.0, 2.1.2, JRuby and REE 1.8.7.
64
-
65
- == Contributing
66
-
67
- If you think we're letting some rules about valid email formats slip through the cracks, don't just update the Regex.
68
- Instead, add a failing test, and demonstrate that the described email address should be treated differently. A link to an appropriate RFC is the best way to do this.
69
- Then change the gem code to make the test pass.
70
-
71
- describe "i_think_this_is_not_a_v@lid_email_addre.ss" do
72
- # According to http://..., this email address IS NOT valid.
73
- it { should have_errors_on_email.because("does not appear to be valid") }
74
- end
75
- describe "i_think_this_is_a_v@lid_email_addre.ss" do
76
- # According to http://..., this email address IS valid.
77
- it { should_not have_errors_on_email }
78
- end
79
-
80
- Yes, our Rspec syntax is that simple!
81
-
82
- == Homepage
83
-
84
- * https://github.com/validates-email-format-of/validates_email_format_of
85
-
86
- == Credits
87
-
88
- Written by Alex Dunae (dunae.ca), 2006-22.
89
-
90
- Many thanks to the plugin's recent contributors: https://github.com/alexdunae/validates_email_format_of/contributors
91
-
92
- Thanks to Francis Hwang (http://fhwang.net/) at Diversion Media for creating the 1.1 update.
93
-
94
- Thanks to Travis Sinnott for creating the 1.3 update.
95
-
96
- Thanks to Denis Ahearn at Riverock Technologies (http://www.riverocktech.com/) for creating the 1.4 update.
97
-
98
- Thanks to George Anderson (http://github.com/george) and 'history' (http://github.com/history) for creating the 1.4.1 update.
99
-
100
- Thanks to Isaac Betesh (https://github.com/betesh) for converting tests to Rspec and refactoring for version 1.6.0.