coppertone 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|