coppertone 0.0.6 → 0.0.7
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 +4 -4
- data/.rubocop.yml +20 -0
- data/.travis.yml +5 -8
- data/README.md +4 -4
- data/Rakefile +6 -6
- data/coppertone.gemspec +2 -2
- data/lib/coppertone/directive.rb +1 -1
- data/lib/coppertone/domain_spec.rb +3 -3
- data/lib/coppertone/ip_address_wrapper.rb +8 -8
- data/lib/coppertone/macro_context.rb +6 -5
- data/lib/coppertone/macro_string/macro_expand.rb +9 -9
- data/lib/coppertone/macro_string/macro_parser.rb +5 -5
- data/lib/coppertone/macro_string/macro_static_expand.rb +1 -1
- data/lib/coppertone/macro_string.rb +1 -1
- data/lib/coppertone/mechanism/a.rb +1 -1
- data/lib/coppertone/mechanism/all.rb +2 -2
- data/lib/coppertone/mechanism/cidr_parser.rb +3 -2
- data/lib/coppertone/mechanism/domain_spec_mechanism.rb +4 -3
- data/lib/coppertone/mechanism/domain_spec_optional.rb +3 -2
- data/lib/coppertone/mechanism/domain_spec_required.rb +4 -3
- data/lib/coppertone/mechanism/domain_spec_with_dual_cidr.rb +10 -8
- data/lib/coppertone/mechanism/exists.rb +1 -1
- data/lib/coppertone/mechanism/include.rb +3 -3
- data/lib/coppertone/mechanism/include_matcher.rb +4 -4
- data/lib/coppertone/mechanism/ip4.rb +1 -1
- data/lib/coppertone/mechanism/ip6.rb +1 -1
- data/lib/coppertone/mechanism/ip_mechanism.rb +9 -9
- data/lib/coppertone/mechanism/mx.rb +2 -2
- data/lib/coppertone/mechanism/ptr.rb +3 -3
- data/lib/coppertone/mechanism.rb +1 -1
- data/lib/coppertone/modifier/base.rb +3 -2
- data/lib/coppertone/modifier/exp.rb +2 -1
- data/lib/coppertone/modifier/redirect.rb +3 -2
- data/lib/coppertone/modifier/unknown.rb +3 -1
- data/lib/coppertone/null_macro_context.rb +2 -2
- data/lib/coppertone/record.rb +6 -4
- data/lib/coppertone/record_evaluator.rb +1 -1
- data/lib/coppertone/record_finder.rb +2 -2
- data/lib/coppertone/record_term_parser.rb +3 -3
- data/lib/coppertone/request.rb +12 -2
- data/lib/coppertone/request_count_limiter.rb +1 -1
- data/lib/coppertone/sender_identity.rb +4 -4
- data/lib/coppertone/terms_parser.rb +1 -1
- data/lib/coppertone/utils/domain_utils.rb +14 -5
- data/lib/coppertone/utils/ip_in_domain_checker.rb +4 -1
- data/lib/coppertone/utils/validated_domain_finder.rb +1 -1
- data/lib/coppertone/version.rb +2 -2
- data/lib/coppertone.rb +1 -0
- data/spec/mechanism/a_spec.rb +9 -9
- data/spec/mechanism/exists_spec.rb +5 -5
- data/spec/mechanism/ip6_spec.rb +0 -1
- data/spec/open_spf/ALL_mechanism_syntax_spec.rb +0 -1
- data/spec/open_spf/A_mechanism_syntax_spec.rb +0 -1
- data/spec/open_spf/EXISTS_mechanism_syntax_spec.rb +0 -1
- data/spec/open_spf/IP4_mechanism_syntax_spec.rb +0 -1
- data/spec/open_spf/IP6_mechanism_syntax_spec.rb +0 -1
- data/spec/open_spf/Include_mechanism_semantics_and_syntax_spec.rb +0 -1
- data/spec/open_spf/Initial_processing_spec.rb +0 -1
- data/spec/open_spf/MX_mechanism_syntax_spec.rb +0 -1
- data/spec/open_spf/Macro_expansion_rules_spec.rb +0 -1
- data/spec/open_spf/PTR_mechanism_syntax_spec.rb +0 -1
- data/spec/open_spf/Processing_limits_spec.rb +0 -1
- data/spec/open_spf/Record_evaluation_spec.rb +0 -1
- data/spec/open_spf/Record_lookup_spec.rb +0 -1
- data/spec/open_spf/Selecting_records_spec.rb +0 -1
- data/spec/open_spf/Semantics_of_exp_and_other_modifiers_spec.rb +0 -1
- data/spec/open_spf/Test_cases_from_implementation_bugs_spec.rb +0 -1
- data/spec/record_spec.rb +5 -5
- data/spec/utils/domain_utils_spec.rb +69 -5
- metadata +4 -3
data/lib/coppertone/mechanism.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
module Coppertone
|
2
|
-
class Modifier
|
2
|
+
class Modifier
|
3
|
+
# Base class including logic common to modifiers
|
3
4
|
class Base < Modifier
|
4
5
|
attr_reader :domain_spec
|
5
6
|
def initialize(attributes)
|
6
7
|
super(attributes)
|
7
|
-
|
8
|
+
raise InvalidModifierError if attributes.blank?
|
8
9
|
@domain_spec = Coppertone::DomainSpec.new(attributes)
|
9
10
|
rescue Coppertone::MacroStringParsingError
|
10
11
|
raise Coppertone::InvalidModifierError
|
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'coppertone/modifier/base'
|
2
2
|
|
3
3
|
module Coppertone
|
4
|
-
class Modifier
|
4
|
+
class Modifier
|
5
|
+
# Exp modifier - specifying a message to be returned in case of failure
|
5
6
|
class Exp < Coppertone::Modifier::Base
|
6
7
|
def self.create(attributes)
|
7
8
|
new(attributes)
|
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'coppertone/modifier/base'
|
2
2
|
|
3
3
|
module Coppertone
|
4
|
-
class Modifier
|
4
|
+
class Modifier
|
5
|
+
# A Redirect modifier found in an SPF record.
|
5
6
|
class Redirect < Coppertone::Modifier::Base
|
6
7
|
def self.create(attributes)
|
7
8
|
new(attributes)
|
@@ -17,7 +18,7 @@ module Coppertone
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def target_domain
|
20
|
-
|
21
|
+
raise NeedsContextError if context_dependent?
|
21
22
|
arguments
|
22
23
|
end
|
23
24
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Coppertone
|
2
|
-
class Modifier
|
2
|
+
class Modifier
|
3
|
+
# Object representing unknown modifiers - those that aren't explicitly
|
4
|
+
# defined by the SPF spec
|
3
5
|
class Unknown < Modifier
|
4
6
|
attr_reader :label
|
5
7
|
def initialize(label, attributes)
|
@@ -5,11 +5,11 @@ module Coppertone
|
|
5
5
|
RESERVED_REGEXP = Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
|
6
6
|
%w(s l o d i p v h c r t).each do |m|
|
7
7
|
define_method(m.upcase) do
|
8
|
-
|
8
|
+
raise ArgumentError
|
9
9
|
end
|
10
10
|
|
11
11
|
define_method(m) do
|
12
|
-
|
12
|
+
raise ArgumentError
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
data/lib/coppertone/record.rb
CHANGED
@@ -33,7 +33,7 @@ module Coppertone
|
|
33
33
|
@dns_lookup_term_count ||=
|
34
34
|
begin
|
35
35
|
base = redirect.nil? ? 0 : 1
|
36
|
-
base + directives.
|
36
|
+
base + directives.count(&:dns_lookup_term?)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
@@ -60,7 +60,9 @@ module Coppertone
|
|
60
60
|
|
61
61
|
def netblock_mechanisms
|
62
62
|
@netblock_mechanisms ||=
|
63
|
-
directives.select
|
63
|
+
directives.select do |d|
|
64
|
+
d.mechanism.is_a?(Coppertone::Mechanism::IPMechanism)
|
65
|
+
end
|
64
66
|
end
|
65
67
|
|
66
68
|
def netblocks_only?
|
@@ -84,7 +86,7 @@ module Coppertone
|
|
84
86
|
end
|
85
87
|
|
86
88
|
KNOWN_MODS =
|
87
|
-
[Coppertone::Modifier::Exp, Coppertone::Modifier::Redirect]
|
89
|
+
[Coppertone::Modifier::Exp, Coppertone::Modifier::Redirect].freeze
|
88
90
|
def unknown_modifiers
|
89
91
|
@unknown_modifiers ||=
|
90
92
|
modifiers.select { |m| KNOWN_MODS.select { |k| m.is_a?(k) }.empty? }
|
@@ -92,7 +94,7 @@ module Coppertone
|
|
92
94
|
|
93
95
|
def find_modifier(klass)
|
94
96
|
arr = modifiers.select { |m| m.is_a?(klass) }
|
95
|
-
|
97
|
+
raise DuplicateModifierError if arr.size > 1
|
96
98
|
arr.first
|
97
99
|
end
|
98
100
|
|
@@ -53,7 +53,7 @@ module Coppertone
|
|
53
53
|
finder =
|
54
54
|
Coppertone::RedirectRecordFinder.new(record.redirect, macro_context,
|
55
55
|
request_context)
|
56
|
-
|
56
|
+
raise InvalidRedirectError unless finder.target && finder.record
|
57
57
|
rc = macro_context.with_domain(finder.target)
|
58
58
|
RecordEvaluator.new(finder.record).evaluate(rc, request_context)
|
59
59
|
end
|
@@ -22,7 +22,7 @@ module Coppertone
|
|
22
22
|
begin
|
23
23
|
if Coppertone::Utils::DomainUtils.valid?(domain)
|
24
24
|
dns_client.fetch_txt_records(domain).map { |r| r[:text] }
|
25
|
-
|
25
|
+
.select { |r| Record.record?(r) }
|
26
26
|
else
|
27
27
|
[]
|
28
28
|
end
|
@@ -30,7 +30,7 @@ module Coppertone
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def validate_txt_records
|
33
|
-
|
33
|
+
raise AmbiguousSpfRecordError if txt_records.size > 1
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Coppertone
|
2
2
|
# Parses a record into terms
|
3
3
|
class RecordTermParser
|
4
|
-
VERSION_STR =
|
4
|
+
VERSION_STR = 'v=spf1'.freeze
|
5
5
|
RECORD_REGEXP = /\A#{VERSION_STR}(\s|\z)/i
|
6
6
|
ALLOWED_CHARACTERS = /\A([\x21-\x7e ]+)\z/
|
7
7
|
|
@@ -12,8 +12,8 @@ module Coppertone
|
|
12
12
|
|
13
13
|
attr_reader :text, :terms
|
14
14
|
def initialize(text)
|
15
|
-
|
16
|
-
|
15
|
+
raise RecordParsingError unless self.class.record?(text)
|
16
|
+
raise RecordParsingError unless ALLOWED_CHARACTERS.match(text)
|
17
17
|
@text = text
|
18
18
|
@terms = Coppertone::TermsParser.new(terms_segment).terms
|
19
19
|
end
|
data/lib/coppertone/request.rb
CHANGED
@@ -11,12 +11,22 @@ module Coppertone
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def authenticate
|
14
|
+
process_helo || process_mailfrom || default_value
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_helo
|
14
18
|
check_spf_for_helo
|
15
|
-
return
|
19
|
+
return nil if helo_result && helo_result.none?
|
20
|
+
helo_result
|
21
|
+
end
|
16
22
|
|
23
|
+
def process_mailfrom
|
17
24
|
check_spf_for_mailfrom
|
18
|
-
return
|
25
|
+
return nil if mailfrom_result && mailfrom_result.none?
|
26
|
+
mailfrom_result
|
27
|
+
end
|
19
28
|
|
29
|
+
def default_value
|
20
30
|
no_matching_record? ? Result.none : Result.neutral
|
21
31
|
end
|
22
32
|
|
@@ -3,7 +3,7 @@ module Coppertone
|
|
3
3
|
# Parses the identity and ensures validity. Also has accessor methods
|
4
4
|
# for the macro letters.
|
5
5
|
class SenderIdentity
|
6
|
-
DEFAULT_LOCALPART = 'postmaster'
|
6
|
+
DEFAULT_LOCALPART = 'postmaster'.freeze
|
7
7
|
EMAIL_ADDRESS_SPLIT_REGEXP = /^(.*)@(.*?)$/
|
8
8
|
|
9
9
|
attr_reader :sender, :localpart, :domain
|
@@ -12,9 +12,9 @@ module Coppertone
|
|
12
12
|
initialize_localpart_and_domain
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
alias s sender
|
16
|
+
alias l localpart
|
17
|
+
alias o domain
|
18
18
|
|
19
19
|
private
|
20
20
|
|
@@ -11,6 +11,7 @@ module Coppertone
|
|
11
11
|
return false if labels.length <= 1
|
12
12
|
return false if domain.length > 253
|
13
13
|
return false if labels.any? { |l| !valid_label?(l) }
|
14
|
+
return false unless valid_tld?(labels.last)
|
14
15
|
true
|
15
16
|
end
|
16
17
|
|
@@ -22,18 +23,19 @@ module Coppertone
|
|
22
23
|
labels = to_labels(domain)
|
23
24
|
return '.' if labels.size == 1
|
24
25
|
labels.shift
|
25
|
-
|
26
|
+
labels.join('.')
|
26
27
|
end
|
27
28
|
|
28
29
|
def self.to_ascii_labels(domain)
|
29
|
-
Addressable::IDNA.to_ascii(domain).split('.').map
|
30
|
+
Addressable::IDNA.to_ascii(domain).split('.').map(&:downcase)
|
30
31
|
end
|
31
32
|
|
32
33
|
def self.normalized_domain(domain)
|
33
34
|
to_ascii_labels(domain).join('.')
|
34
35
|
end
|
35
36
|
|
36
|
-
|
37
|
+
NO_DASH_NONNUMERIC_REGEXP = /\A[a-zA-Z0-9]*[a-zA-Z]+[a-zA-Z0-9]*\z/
|
38
|
+
NO_DASH_REGEXP = /\A[a-zA-Z0-9]+\z/
|
37
39
|
DASH_REGEXP = /\A[a-zA-Z0-9]+\-[a-zA-Z0-9\-]*[a-zA-Z0-9]+\z/
|
38
40
|
|
39
41
|
def self.valid_hostname_label?(l)
|
@@ -41,13 +43,20 @@ module Coppertone
|
|
41
43
|
NO_DASH_REGEXP.match(l) || DASH_REGEXP.match(l)
|
42
44
|
end
|
43
45
|
|
46
|
+
def self.valid_tld?(l)
|
47
|
+
return false unless valid_label?(l)
|
48
|
+
NO_DASH_NONNUMERIC_REGEXP.match(l) || DASH_REGEXP.match(l)
|
49
|
+
end
|
50
|
+
|
44
51
|
def self.valid_ldh_domain?(domain)
|
45
52
|
return false unless valid?(domain)
|
46
|
-
to_ascii_labels(domain)
|
53
|
+
labels = to_ascii_labels(domain)
|
54
|
+
return false unless valid_tld?(labels.last)
|
55
|
+
labels.all? { |l| valid_hostname_label?(l) }
|
47
56
|
end
|
48
57
|
|
49
58
|
def self.valid_label?(l)
|
50
|
-
(l.length >= 0) && (l.length <= 63)
|
59
|
+
(l.length >= 0) && (l.length <= 63) && !l.match(/\s/)
|
51
60
|
end
|
52
61
|
|
53
62
|
def self.macro_expanded_domain(domain)
|
@@ -1,5 +1,8 @@
|
|
1
1
|
module Coppertone
|
2
|
-
module Utils
|
2
|
+
module Utils
|
3
|
+
# Checks the IP address from the request against an A or AAAA record
|
4
|
+
# for a domain. Takes optional CIDR arguments so the match can
|
5
|
+
# check subnets
|
3
6
|
class IPInDomainChecker
|
4
7
|
def initialize(macro_context, request_context)
|
5
8
|
@macro_context = macro_context
|
data/lib/coppertone/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module Coppertone
|
2
|
-
VERSION = '0.0.
|
1
|
+
module Coppertone
|
2
|
+
VERSION = '0.0.7'.freeze
|
3
3
|
end
|
data/lib/coppertone.rb
CHANGED
data/spec/mechanism/a_spec.rb
CHANGED
@@ -199,15 +199,15 @@ describe Coppertone::Mechanism::A do
|
|
199
199
|
dc = double(:dns_client)
|
200
200
|
allow(dc).to receive(:fetch_a_records)
|
201
201
|
.with(mech_domain).and_return([
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
202
|
+
{
|
203
|
+
type: 'A',
|
204
|
+
address: '74.125.239.117'
|
205
|
+
},
|
206
|
+
{
|
207
|
+
type: 'A',
|
208
|
+
address: '74.125.239.118'
|
209
|
+
}
|
210
|
+
])
|
211
211
|
dc
|
212
212
|
end
|
213
213
|
|
@@ -84,11 +84,11 @@ describe Coppertone::Mechanism::Exists do
|
|
84
84
|
dc = double(:dns_client)
|
85
85
|
allow(dc).to receive(:fetch_a_records)
|
86
86
|
.with(mech_domain).and_return([
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
87
|
+
{
|
88
|
+
type: 'A',
|
89
|
+
address: '74.125.234.117'
|
90
|
+
}
|
91
|
+
])
|
92
92
|
allow(dc).to receive(:fetch_a_records)
|
93
93
|
.with(bad_domain).and_return([])
|
94
94
|
dc
|
data/spec/mechanism/ip6_spec.rb
CHANGED
data/spec/record_spec.rb
CHANGED
@@ -138,9 +138,9 @@ describe Coppertone::Record do
|
|
138
138
|
|
139
139
|
it 'should yield a set of the includes, in order' do
|
140
140
|
r = Coppertone::Record
|
141
|
-
|
141
|
+
.new('v=spf1 include:_spf.domain1.com ip4:1.2.3.4 include:_spf.domain2.com ~all exp=explain._spf.%{d}')
|
142
142
|
includes = r.includes
|
143
|
-
expect(includes.map(&:mechanism).all? {|i| i.is_a?(Coppertone::Mechanism::Include)}).to be_truthy
|
143
|
+
expect(includes.map(&:mechanism).all? { |i| i.is_a?(Coppertone::Mechanism::Include) }).to be_truthy
|
144
144
|
expect(includes.map(&:target_domain)).to eq(%w(_spf.domain1.com _spf.domain2.com))
|
145
145
|
end
|
146
146
|
end
|
@@ -148,19 +148,19 @@ describe Coppertone::Record do
|
|
148
148
|
context '#context_dependent_evaluation?' do
|
149
149
|
it 'should be false when the record is not context dependent' do
|
150
150
|
r = Coppertone::Record
|
151
|
-
|
151
|
+
.new('v=spf1 include:_spf.domain1.com ip4:1.2.3.4 include:_spf.domain2.com ~all exp=explain._spf.source.com')
|
152
152
|
expect(r).not_to be_context_dependent_evaluation
|
153
153
|
end
|
154
154
|
|
155
155
|
it 'should be false when the record has a context dependent redirect' do
|
156
156
|
r = Coppertone::Record
|
157
|
-
|
157
|
+
.new('v=spf1 redirect=explain._spf.%{h}')
|
158
158
|
expect(r).to be_context_dependent_evaluation
|
159
159
|
end
|
160
160
|
|
161
161
|
it 'should be false when the record has a context dependent directive' do
|
162
162
|
r = Coppertone::Record
|
163
|
-
|
163
|
+
.new('v=spf1 include:_spf.domain1.com ip4:1.2.3.4 include:_spf.%{l}.domain2.com ~all exp=explain._spf.static.com')
|
164
164
|
expect(r).to be_context_dependent_evaluation
|
165
165
|
end
|
166
166
|
end
|
@@ -11,6 +11,11 @@ describe Coppertone::Utils::DomainUtils do
|
|
11
11
|
expect(subject.valid?('abc.bit.ly')).to eq(true)
|
12
12
|
end
|
13
13
|
|
14
|
+
it 'should validate domains with numeric labels' do
|
15
|
+
expect(subject.valid?('abc.126.com')).to eq(true)
|
16
|
+
expect(subject.valid?('37.com')).to eq(true)
|
17
|
+
end
|
18
|
+
|
14
19
|
it 'should validate domains when they are dot-terminated' do
|
15
20
|
expect(subject.valid?('gmail.com.')).to eq(true)
|
16
21
|
expect(subject.valid?('fermion.mit.edu.')).to eq(true)
|
@@ -26,6 +31,19 @@ describe Coppertone::Utils::DomainUtils do
|
|
26
31
|
expect(subject.valid?('清华大学.cn')).to eq(true)
|
27
32
|
expect(subject.valid?('ジェーピーニック.jp')).to eq(true)
|
28
33
|
end
|
34
|
+
|
35
|
+
it 'should reject labels containing whitespace' do
|
36
|
+
expect(subject.valid?('mail mike.net')).to eq(false)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should validate domains with underscores' do
|
40
|
+
expect(subject.valid?('_dmarc.126.com')).to eq(true)
|
41
|
+
expect(subject.valid?('abcd._domainkey.gmail.com')).to eq(true)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should reject IP addresses' do
|
45
|
+
expect(subject.valid?('192.38.7.14')).to eq(false)
|
46
|
+
end
|
29
47
|
end
|
30
48
|
|
31
49
|
context '#macro_expanded_domain' do
|
@@ -53,7 +71,7 @@ describe Coppertone::Utils::DomainUtils do
|
|
53
71
|
end
|
54
72
|
|
55
73
|
it 'truncates overlong domains' do
|
56
|
-
domain_candidate_labels = 50
|
74
|
+
domain_candidate_labels = Array.new(50) { "a-#{SecureRandom.hex(2)}" }
|
57
75
|
domain_candidate = domain_candidate_labels.join('.')
|
58
76
|
truncated_labels = domain_candidate_labels.drop(14)
|
59
77
|
truncated_domain = truncated_labels.join('.')
|
@@ -65,7 +83,8 @@ describe Coppertone::Utils::DomainUtils do
|
|
65
83
|
|
66
84
|
context '#parent_domain' do
|
67
85
|
it 'should handle hostnames correctly' do
|
68
|
-
expect(subject.parent_domain('abc.xyz.example.com'))
|
86
|
+
expect(subject.parent_domain('abc.xyz.example.com'))
|
87
|
+
.to eq('xyz.example.com')
|
69
88
|
end
|
70
89
|
|
71
90
|
it 'should handle TLDs correctly' do
|
@@ -75,12 +94,57 @@ describe Coppertone::Utils::DomainUtils do
|
|
75
94
|
|
76
95
|
context '#normalized_domain' do
|
77
96
|
it 'should handle ASCII hostnames correctly' do
|
78
|
-
expect(subject.normalized_domain('abc.xyz.example.com'))
|
79
|
-
|
97
|
+
expect(subject.normalized_domain('abc.xyz.example.com'))
|
98
|
+
.to eq('abc.xyz.example.com')
|
99
|
+
expect(subject.normalized_domain('ABc.xYz.exAMPle.COM'))
|
100
|
+
.to eq('abc.xyz.example.com')
|
80
101
|
end
|
81
102
|
|
82
103
|
it 'should handle Unicode domains correctly' do
|
83
|
-
expect(subject.normalized_domain('FERMIon.清华大学.cn'))
|
104
|
+
expect(subject.normalized_domain('FERMIon.清华大学.cn'))
|
105
|
+
.to eq('fermion.xn--xkry9kk1bz66a.cn')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context '#valid_ldh_domain?' do
|
110
|
+
it 'should validate standard domains' do
|
111
|
+
expect(subject.valid_ldh_domain?('gmail.com')).to eq(true)
|
112
|
+
expect(subject.valid_ldh_domain?('fermion.mit.edu')).to eq(true)
|
113
|
+
expect(subject.valid_ldh_domain?('abc.bit.ly')).to eq(true)
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should validate domains with numeric labels' do
|
117
|
+
expect(subject.valid_ldh_domain?('abc.126.com')).to eq(true)
|
118
|
+
expect(subject.valid_ldh_domain?('37.com')).to eq(true)
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should validate domains when they are dot-terminated' do
|
122
|
+
expect(subject.valid_ldh_domain?('gmail.com.')).to eq(true)
|
123
|
+
expect(subject.valid_ldh_domain?('fermion.mit.edu.')).to eq(true)
|
124
|
+
expect(subject.valid_ldh_domain?('abc.bit.ly.')).to eq(true)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should reject domains with less than two labels' do
|
128
|
+
expect(subject.valid_ldh_domain?('')).to eq(false)
|
129
|
+
expect(subject.valid_ldh_domain?('one')).to eq(false)
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'should handle IDNA domains' do
|
133
|
+
expect(subject.valid_ldh_domain?('清华大学.cn')).to eq(true)
|
134
|
+
expect(subject.valid_ldh_domain?('ジェーピーニック.jp')).to eq(true)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should reject labels containing whitespace' do
|
138
|
+
expect(subject.valid_ldh_domain?('mail mike.net')).to eq(false)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'should reject domains with underscores' do
|
142
|
+
expect(subject.valid_ldh_domain?('_dmarc.126.com')).to eq(false)
|
143
|
+
expect(subject.valid_ldh_domain?('abcd._domainkey.gmail.com')).to eq(false)
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'should reject IP addresses' do
|
147
|
+
expect(subject.valid_ldh_domain?('192.38.7.14')).to eq(false)
|
84
148
|
end
|
85
149
|
end
|
86
150
|
end
|