Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
* develop:
  Bump version to `2.2.1`
  Add `rubocop-rake` to dev dependencies
  Enforce domain character limit
  Allow mailbox-only addresses  in `:rfc` mode
  Fix regexp for numeric subdomains (fixes #68)
  [specs] Add tests for numeric subdomains
  • Loading branch information
karlwilbur committed Dec 10, 2020
2 parents a1b0950 + 2f2f131 commit 7c0f32f
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 16 deletions.
6 changes: 5 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#
# See https://github.com/rubocop-hq/rubocop/blob/master/manual/configuration.md
require:
- rubocop-rake
- rubocop-rspec

AllCops:
Expand All @@ -23,7 +24,7 @@ Bundler/GemComment:
Bundler/OrderedGems:
TreatCommentsAsGroupSeparators: true

Capybara/FeatureMethods:
RSpec/Capybara/FeatureMethods:
Enabled: true
EnabledMethods:
- feature
Expand Down Expand Up @@ -148,6 +149,9 @@ Metrics/BlockLength:
- 'spec/**/*.rb'
- '**/*.gemspec'

Metrics/ClassLength:
Max: 150

Metrics/MethodLength:
Max: 15

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ This file is used to list changes made in `email_validator`.
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## 2.2.1 (2020-12-10)

* [karlwilbur] - Modify regexp to:
- allow numeric-only hosts [#68]
- allow mailbox-only addresses in `:rfc` mode
- enforce the 255-char domain limit (not in `:loose` mode unless using `:domain`)

## 2.2.0 (2020-12-09)

* [karlwilbur] - Rename `:strict` -> `:rfc`; `:moderate` -> `:strict`
Expand Down
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ group :development do
gem 'rubocop', '>= 1.0'
# Rubocop performance evaluation
gem 'rubocop-performance', '>= 1.0'
# Rubocop Rake evaluation
gem 'rubocop-rake'
# Rubocop RSpec evaluation
gem 'rubocop-rspec', '>= 2.0'

Expand Down
2 changes: 1 addition & 1 deletion email_validator.gemspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Gem::Specification.new do |s|
s.name = 'email_validator'
s.version = '2.2.0'
s.version = '2.2.1'
s.authors = ['Brian Alexander', 'Karl Wilbur']
s.summary = 'An email validator for Rails 3+.'
s.description = 'An email validator for Rails 3+. See homepage for details: http://github.com/K-and-R/email_validator'
Expand Down
62 changes: 53 additions & 9 deletions lib/email_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,31 @@ def invalid?(value, options = {})
# https://tools.ietf.org/html/rfc5321 : 4.1.2. Command Argument Syntax
def regexp(options = {})
options = parse_options(options)
if options[:mode] == :loose
return /\A[^\s]+@[^\s]+\z/ if options[:domain].nil?
return /\A[^\s]+@#{domain_pattern(options)}\z/
case options[:mode]
when :loose
loose_regexp(options)
when :rfc
rfc_regexp(options)
else
strict_regexp(options)
end
/\A(?>#{local_part_pattern})@#{domain_pattern(options)}\z/i
end

protected

def loose_regexp(options = {})
return /\A[^\s]+@[^\s]+\z/ if options[:domain].nil?
/\A[^\s]+@#{domain_part_pattern(options)}\z/
end

def strict_regexp(options = {})
/\A(?>#{local_part_pattern})@#{domain_part_pattern(options)}\z/i
end

def rfc_regexp(options = {})
/\A(?>#{local_part_pattern})(?:@#{domain_part_pattern(options)})?\z/i
end

def alpha
'[[:alpha:]]'
end
Expand All @@ -64,8 +80,32 @@ def address_literal
"\\[(?:#{ipv4}|#{ipv6})\\]"
end

def label_pattern
"#{alpha}(?:#{alnumhy}{,62}#{alnum})?"
def host_label_pattern
"#{alnum}(?:#{alnumhy}{,61}#{alnum})?"
end

# splitting this up into separate regex pattern for performance; let's not
# try the "contains" pattern unless we have to
def domain_label_pattern
'(?=[^.]{1,63}(?:\.|$))' \
'(?:' \
"#{alpha}" \
"|#{domain_label_starts_with_a_letter_pattern}" \
"|#{domain_label_ends_with_a_letter_pattern}" \
"|#{domain_label_contains_a_letter_pattern}" \
')'
end

def domain_label_starts_with_a_letter_pattern
"#{alpha}#{alnumhy}{,61}#{alnum}"
end

def domain_label_ends_with_a_letter_pattern
"#{alnum}#{alnumhy}{,61}#{alpha}"
end

def domain_label_contains_a_letter_pattern
"(?:[[:digit:]])(?:[[:digit:]]|-)*#{alpha}#{alnumhy}*#{alnum}"
end

def atom_char
Expand All @@ -79,10 +119,14 @@ def local_part_pattern
"#{atom_char}(?:\\.?#{atom_char}){,63}"
end

def domain_pattern(options)
def domain_part_pattern(options)
return options[:domain].sub(/\./, '\.') if options[:domain].present?
return "(?:#{label_pattern}\\.)+#{label_pattern}" if options[:require_fqdn]
"(?:#{address_literal}|(?:#{label_pattern}\\.)*#{label_pattern})"
return fqdn_pattern if options[:require_fqdn]
"(?=.{1,255}$)(?:#{address_literal}|(?:#{host_label_pattern}\\.)*#{domain_label_pattern})"
end

def fqdn_pattern
"(?=.{1,255}$)(?:#{host_label_pattern}\\.)*#{domain_label_pattern}\\.#{domain_label_pattern}"
end

private
Expand Down
140 changes: 135 additions & 5 deletions spec/email_validator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,13 @@ class DefaultUserWithMessage < TestModel
'[email protected]',
'mixed-1234-in-{+^}[email protected]',
'partially."quoted"@sld.com',
'areallylongnameaasdfasdfasdfasdf@asdfasdfasdfasdfasdf.ab.cd.ef.gh.co.ca'
'areallylongnameaasdfasdfasdfasdf@asdfasdfasdfasdfasdf.ab.cd.ef.gh.co.ca',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]'
]).flatten.each do |email|
context 'when using defaults' do
it "'#{email}' should be valid" do
Expand Down Expand Up @@ -260,6 +266,132 @@ class DefaultUserWithMessage < TestModel
end
end

context 'when given the numeric domain' do
[
'[email protected]'
].each do |email|
context 'when using defaults' do
it "'#{email}' should be valid" do
expect(DefaultUser.new(:email => email)).to be_valid
end

it "'#{email}' should be valid using EmailValidator.valid?" do
expect(described_class).to be_valid(email)
end

it "'#{email}' should not be invalid using EmailValidator.invalid?" do
expect(described_class).not_to be_invalid(email)
end

it "'#{email}' should match the regexp" do
expect(!!(email.strip =~ described_class.regexp)).to be(true)
end
end

context 'when in `:strict` mode' do
it "'#{email}' should not be valid" do
expect(StrictUser.new(:email => email)).not_to be_valid
end

it "'#{email}' should not be valid using EmailValidator.valid?" do
expect(described_class).not_to be_valid(email, :mode => :strict)
end

it "'#{email}' should be invalid using EmailValidator.invalid?" do
expect(described_class).to be_invalid(email, :mode => :strict)
end

it "'#{email}' should not match the regexp" do
expect(!!(email.strip =~ described_class.regexp(:mode => :strict))).to be(false)
end
end

context 'when in `:rfc` mode' do
it "'#{email}' should be valid" do
expect(RfcUser.new(:email => email)).to be_valid
end

it "'#{email}' should be valid using EmailValidator.valid?" do
expect(described_class).to be_valid(email, :mode => :rfc)
end

it "'#{email}' should not be invalid using EmailValidator.invalid?" do
expect(described_class).not_to be_invalid(email, :mode => :rfc)
end

it "'#{email}' should match the regexp" do
expect(!!(email.strip =~ described_class.regexp(:mode => :rfc))).to be(true)
end
end
end
end

context 'when given the valid mailbox-only email' do
valid_includable.map { |k, v| [
"user-#{v}-#{k}-name"
]}.concat(valid_beginable.map { |k, v| [
"#{v}-start-with-#{k}-user"
]}).concat(valid_endable.map { |k, v| [
"end-with-#{k}-#{v}"
]}).concat([
'user'
]).flatten.each do |email|
context 'when using defaults' do
it "'#{email}' should not be valid" do
expect(DefaultUser.new(:email => email)).not_to be_valid
end

it "'#{email}' should not be valid using EmailValidator.valid?" do
expect(described_class).not_to be_valid(email)
end

it "'#{email}' should be invalid using EmailValidator.invalid?" do
expect(described_class).to be_invalid(email)
end

it "'#{email}' should not match the regexp" do
expect(!!(email.strip =~ described_class.regexp)).to be(false)
end
end

context 'when in `:strict` mode' do
it "'#{email}' should not be valid" do
expect(StrictUser.new(:email => email)).not_to be_valid
end

it "'#{email}' should not be valid using EmailValidator.valid?" do
expect(described_class).not_to be_valid(email, :mode => :strict)
end

it "'#{email}' should be invalid using EmailValidator.invalid?" do
expect(described_class).to be_invalid(email, :mode => :strict)
end

it "'#{email}' should not match the regexp" do
expect(!!(email.strip =~ described_class.regexp(:mode => :strict))).to be(false)
end
end

context 'when in `:rfc` mode' do
it "'#{email}' should be valid" do
expect(RfcUser.new(:email => email)).to be_valid
end

it "'#{email}' should be valid using EmailValidator.valid?" do
expect(described_class).to be_valid(email, :mode => :rfc)
end

it "'#{email}' should not be invalid using EmailValidator.invalid?" do
expect(described_class).not_to be_invalid(email, :mode => :rfc)
end

it "'#{email}' should match the regexp" do
expect(!!(email.strip =~ described_class.regexp(:mode => :rfc))).to be(true)
end
end
end
end

context 'when given the valid IP address email' do
[
'bracketed-IP@[127.0.0.1]',
Expand Down Expand Up @@ -334,8 +466,6 @@ class DefaultUserWithMessage < TestModel
'[email protected]@example.com',
'[email protected]',
'missing-tld@sld.',
'[email protected]',
'[email protected]',
'unbracketed-IPv6@abcd:ef01:1234:5678:9abc:def0:1234:5678',
'unbracketed-and-labled-IPv6@IPv6:abcd:ef01:1234:5678:9abc:def0:1234:5678',
'bracketed-and-unlabeled-IPv6@[abcd:ef01:1234:5678:9abc:def0:1234:5678]',
Expand All @@ -347,6 +477,7 @@ class DefaultUserWithMessage < TestModel
'[email protected]',
'[email protected]',
'the-local-part-is-invalid-if-it-is-longer-than-sixty-four-characters@sld.dev',
"domain-too-long@t#{".#{'o' * 63}" * 5}.long",
"[email protected]<script>alert('hello')</script>"
]).flatten.each do |email|
context 'when using defaults' do
Expand Down Expand Up @@ -504,8 +635,7 @@ class DefaultUserWithMessage < TestModel
'@bar.com',
'test@',
'@missing-local.dev',
' ',
'missing-at-sign.dev'
' '
].each do |email|
context 'when using defaults' do
it "'#{email}' should not be valid" do
Expand Down

0 comments on commit 7c0f32f

Please sign in to comment.