validates_email_format_of 1.7.2 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.