coppertone 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/.travis.yml +7 -0
- data/Gemfile +7 -0
- data/LICENSE +201 -0
- data/README.md +58 -0
- data/Rakefile +140 -0
- data/coppertone.gemspec +27 -0
- data/lib/coppertone/class_builder.rb +20 -0
- data/lib/coppertone/directive.rb +38 -0
- data/lib/coppertone/dns/error.rb +9 -0
- data/lib/coppertone/dns/mock_client.rb +106 -0
- data/lib/coppertone/dns/resolv_client.rb +110 -0
- data/lib/coppertone/dns.rb +3 -0
- data/lib/coppertone/domain_spec.rb +45 -0
- data/lib/coppertone/error.rb +29 -0
- data/lib/coppertone/ip_address_wrapper.rb +75 -0
- data/lib/coppertone/macro_context.rb +67 -0
- data/lib/coppertone/macro_string/macro_expand.rb +84 -0
- data/lib/coppertone/macro_string/macro_literal.rb +24 -0
- data/lib/coppertone/macro_string/macro_parser.rb +62 -0
- data/lib/coppertone/macro_string/macro_static_expand.rb +52 -0
- data/lib/coppertone/macro_string.rb +31 -0
- data/lib/coppertone/mechanism/a.rb +16 -0
- data/lib/coppertone/mechanism/all.rb +24 -0
- data/lib/coppertone/mechanism/cidr_parser.rb +14 -0
- data/lib/coppertone/mechanism/domain_spec_mechanism.rb +18 -0
- data/lib/coppertone/mechanism/domain_spec_optional.rb +46 -0
- data/lib/coppertone/mechanism/domain_spec_required.rb +37 -0
- data/lib/coppertone/mechanism/domain_spec_with_dual_cidr.rb +114 -0
- data/lib/coppertone/mechanism/exists.rb +14 -0
- data/lib/coppertone/mechanism/include.rb +18 -0
- data/lib/coppertone/mechanism/include_matcher.rb +34 -0
- data/lib/coppertone/mechanism/ip4.rb +13 -0
- data/lib/coppertone/mechanism/ip6.rb +13 -0
- data/lib/coppertone/mechanism/ip_mechanism.rb +48 -0
- data/lib/coppertone/mechanism/mx.rb +40 -0
- data/lib/coppertone/mechanism/ptr.rb +17 -0
- data/lib/coppertone/mechanism.rb +32 -0
- data/lib/coppertone/modifier/base.rb +24 -0
- data/lib/coppertone/modifier/exp.rb +34 -0
- data/lib/coppertone/modifier/redirect.rb +17 -0
- data/lib/coppertone/modifier/unknown.rb +16 -0
- data/lib/coppertone/modifier.rb +30 -0
- data/lib/coppertone/qualifier.rb +45 -0
- data/lib/coppertone/record.rb +86 -0
- data/lib/coppertone/record_evaluator.rb +63 -0
- data/lib/coppertone/record_finder.rb +34 -0
- data/lib/coppertone/request.rb +68 -0
- data/lib/coppertone/request_context.rb +67 -0
- data/lib/coppertone/request_count_limiter.rb +36 -0
- data/lib/coppertone/result.rb +50 -0
- data/lib/coppertone/sender_identity.rb +39 -0
- data/lib/coppertone/spf_service.rb +9 -0
- data/lib/coppertone/term.rb +13 -0
- data/lib/coppertone/utils/domain_utils.rb +59 -0
- data/lib/coppertone/utils/host_utils.rb +22 -0
- data/lib/coppertone/utils/ip_in_domain_checker.rb +53 -0
- data/lib/coppertone/utils/validated_domain_finder.rb +40 -0
- data/lib/coppertone/utils.rb +4 -0
- data/lib/coppertone/version.rb +3 -0
- data/lib/coppertone.rb +48 -0
- data/lib/resolv/dns/resource/in/spf.rb +15 -0
- data/spec/directive_spec.rb +41 -0
- data/spec/dns/resolv_client_spec.rb +307 -0
- data/spec/domain_spec_spec.rb +35 -0
- data/spec/ip_address_wrapper_spec.rb +67 -0
- data/spec/macro_context_spec.rb +69 -0
- data/spec/macro_string/macro_expand_spec.rb +79 -0
- data/spec/macro_string/macro_literal_spec.rb +27 -0
- data/spec/macro_string/macro_static_expand_spec.rb +67 -0
- data/spec/macro_string_spec.rb +20 -0
- data/spec/mechanism/a_spec.rb +198 -0
- data/spec/mechanism/all_spec.rb +22 -0
- data/spec/mechanism/exists_spec.rb +91 -0
- data/spec/mechanism/include_spec.rb +43 -0
- data/spec/mechanism/ip4_spec.rb +110 -0
- data/spec/mechanism/ip6_spec.rb +104 -0
- data/spec/mechanism/mx_spec.rb +51 -0
- data/spec/mechanism/ptr_spec.rb +43 -0
- data/spec/mechanism_spec.rb +4 -0
- data/spec/modifier_spec.rb +4 -0
- data/spec/open_spf/ALL_mechanism_syntax_spec.rb +38 -0
- data/spec/open_spf/A_mechanism_syntax_spec.rb +159 -0
- data/spec/open_spf/EXISTS_mechanism_syntax_spec.rb +46 -0
- data/spec/open_spf/IP4_mechanism_syntax_spec.rb +59 -0
- data/spec/open_spf/IP6_mechanism_syntax_spec.rb +60 -0
- data/spec/open_spf/Include_mechanism_semantics_and_syntax_spec.rb +56 -0
- data/spec/open_spf/Initial_processing_spec.rb +77 -0
- data/spec/open_spf/MX_mechanism_syntax_spec.rb +119 -0
- data/spec/open_spf/Macro_expansion_rules_spec.rb +154 -0
- data/spec/open_spf/PTR_mechanism_syntax_spec.rb +42 -0
- data/spec/open_spf/Processing_limits_spec.rb +72 -0
- data/spec/open_spf/Record_evaluation_spec.rb +75 -0
- data/spec/open_spf/Record_lookup_spec.rb +48 -0
- data/spec/open_spf/Selecting_records_spec.rb +61 -0
- data/spec/open_spf/Semantics_of_exp_and_other_modifiers_spec.rb +167 -0
- data/spec/open_spf/Test_cases_from_implementation_bugs_spec.rb +17 -0
- data/spec/qualifier_spec.rb +54 -0
- data/spec/record_evaluator_spec.rb +4 -0
- data/spec/record_finder_spec.rb +4 -0
- data/spec/record_spec.rb +100 -0
- data/spec/request_context_spec.rb +43 -0
- data/spec/request_count_limiter_spec.rb +28 -0
- data/spec/result_spec.rb +4 -0
- data/spec/rfc7208-tests.yml +2548 -0
- data/spec/sender_identity_spec.rb +69 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/term_spec.rb +38 -0
- data/spec/utils/domain_utils_spec.rb +60 -0
- data/spec/utils/host_utils_spec.rb +32 -0
- data/spec/utils/ip_in_domain_checker_spec.rb +4 -0
- data/spec/utils/validated_domain_finder_spec.rb +4 -0
- metadata +306 -0
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Semantics of exp and other modifiers' do
|
4
|
+
let(:zonefile) do
|
5
|
+
{ 'mail.example.com' => [{ 'A' => '1.2.3.4' }], 'e1.example.com' => [{ 'TXT' => 'v=spf1 exp=exp1.example.com redirect=e2.example.com' }], 'e2.example.com' => [{ 'TXT' => 'v=spf1 -all' }], 'e3.example.com' => [{ 'TXT' => 'v=spf1 exp=exp1.example.com redirect=e4.example.com' }], 'e4.example.com' => [{ 'TXT' => 'v=spf1 -all exp=exp2.example.com' }], 'exp1.example.com' => [{ 'TXT' => 'No-see-um' }], 'exp2.example.com' => [{ 'TXT' => 'See me.' }], 'exp3.example.com' => [{ 'TXT' => 'Correct!' }], 'exp4.example.com' => [{ 'TXT' => '%{l} in implementation' }], 'e5.example.com' => [{ 'TXT' => 'v=spf1 1up=foo' }], 'e6.example.com' => [{ 'TXT' => 'v=spf1 =all' }], 'e7.example.com' => [{ 'TXT' => 'v=spf1 include:e3.example.com -all exp=exp3.example.com' }], 'e8.example.com' => [{ 'TXT' => 'v=spf1 -all exp=exp4.example.com' }], 'e9.example.com' => [{ 'TXT' => 'v=spf1 -all foo=%abc' }], 'e10.example.com' => [{ 'TXT' => 'v=spf1 redirect=erehwon.example.com' }], 'e11.example.com' => [{ 'TXT' => 'v=spf1 -all exp=e11msg.example.com' }], 'e11msg.example.com' => [{ 'TXT' => 'Answer a fool according to his folly.' }, { 'TXT' => 'Do not answer a fool according to his folly.' }], 'e12.example.com' => [{ 'TXT' => 'v=spf1 exp= -all' }], 'e13.example.com' => [{ 'TXT' => 'v=spf1 exp=e13msg.example.com -all' }], 'e13msg.example.com' => [{ 'TXT' => 'The %{x}-files.' }], 'e14.example.com' => [{ 'TXT' => 'v=spf1 exp=e13msg.example.com -all exp=e11msg.example.com' }], 'e15.example.com' => [{ 'TXT' => 'v=spf1 redirect=e12.example.com -all redirect=e12.example.com' }], 'e16.example.com' => [{ 'TXT' => 'v=spf1 exp=-all' }], 'e17.example.com' => [{ 'TXT' => 'v=spf1 redirect=-all ?all' }], 'e18.example.com' => [{ 'TXT' => 'v=spf1 ?all redirect=' }], 'e19.example.com' => [{ 'TXT' => 'v=spf1 default=pass' }], 'e20.example.com' => [{ 'TXT' => 'v=spf1 default=+' }], 'e21.example.com' => [{ 'TXT' => 'v=spf1 exp=e21msg.example.com -all' }], 'e21msg.example.com' => ['TIMEOUT'], 'e22.example.com' => [{ 'TXT' => 'v=spf1 exp=mail.example.com -all' }], 'nonascii.example.com' => [{ 'TXT' => 'v=spf1 exp=badexp.example.com -all' }], 'badexp.example.com' => [{ 'TXT' => 'Explanation' }], 'tworecs.example.com' => [{ 'TXT' => 'v=spf1 exp=twoexp.example.com -all' }], 'twoexp.example.com' => [{ 'TXT' => 'one' }, { 'TXT' => 'two' }], 'e23.example.com' => [{ 'TXT' => 'v=spf1 a:erehwon.example.com a:foobar.com exp=nxdomain.com -all' }], 'e24.example.com' => [{ 'TXT' => 'v=spf1 redirect=testimplicit.example.com' }, { 'A' => '192.0.2.1' }], 'testimplicit.example.com' => [{ 'TXT' => 'v=spf1 a -all' }, { 'A' => '192.0.2.2' }] }
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
|
9
|
+
let(:options) { { dns_client: dns_client } }
|
10
|
+
|
11
|
+
it 'If no SPF record is found, or if the target-name is malformed, the result is a "PermError" rather than "None".' do
|
12
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e10.example.com', 'mail.example.com', options)
|
13
|
+
expect(%i(permerror)).to include(result.code)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'when executing "redirect", exp= from the original domain MUST NOT be used.' do
|
17
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e1.example.com', 'mail.example.com', options)
|
18
|
+
expect(%i(fail)).to include(result.code)
|
19
|
+
expect(result.explanation).to eq('DEFAULT')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'redirect = "redirect" "=" domain-spec ' do
|
23
|
+
# A literal application of the grammar causes modifier syntax errors (except for macro syntax) to become unknown-modifier.
|
24
|
+
#
|
25
|
+
# modifier = explanation | redirect | unknown-modifier
|
26
|
+
#
|
27
|
+
# However, it is generally agreed, with precedent in other RFCs, that unknown-modifier should not be "greedy", and should not match known modifier names. There should have been explicit prose to this effect, and some has been proposed as an erratum.
|
28
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e17.example.com', 'mail.example.com', options)
|
29
|
+
expect(%i(permerror)).to include(result.code)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'when executing "include", exp= from the target domain MUST NOT be used.' do
|
33
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e7.example.com', 'mail.example.com', options)
|
34
|
+
expect(%i(fail)).to include(result.code)
|
35
|
+
expect(result.explanation).to eq('Correct!')
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'when executing "redirect", exp= from the original domain MUST NOT be used.' do
|
39
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e3.example.com', 'mail.example.com', options)
|
40
|
+
expect(%i(fail)).to include(result.code)
|
41
|
+
expect(result.explanation).to eq('See me.')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'unknown-modifier = name "=" macro-string name = ALPHA *( ALPHA / DIGIT / "-" / "_" / "." ) ' do
|
45
|
+
# Unknown modifier name must begin with alpha.
|
46
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e5.example.com', 'mail.example.com', options)
|
47
|
+
expect(%i(permerror)).to include(result.code)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'name = ALPHA *( ALPHA / DIGIT / "-" / "_" / "." ) ' do
|
51
|
+
# Unknown modifier name must not be empty.
|
52
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e6.example.com', 'mail.example.com', options)
|
53
|
+
expect(%i(permerror)).to include(result.code)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'An implementation that uses a legal expansion as a sentinel. We cannot check them all, but we can check this one.' do
|
57
|
+
# Spaces are allowed in local-part.
|
58
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'Macro Error@e8.example.com', 'mail.example.com', options)
|
59
|
+
expect(%i(fail)).to include(result.code)
|
60
|
+
expect(result.explanation).to eq('Macro Error in implementation')
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'Ignore exp if multiple TXT records. ' do
|
64
|
+
# If domain-spec is empty, or there are any DNS processing errors (any RCODE other than 0), or if no records are returned, or if more than one record is returned, or if there are syntax errors in the explanation string, then proceed as if no exp modifier was given.
|
65
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e11.example.com', 'mail.example.com', options)
|
66
|
+
expect(%i(fail)).to include(result.code)
|
67
|
+
expect(result.explanation).to eq('DEFAULT')
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'Ignore exp if no TXT records. ' do
|
71
|
+
# If domain-spec is empty, or there are any DNS processing errors (any RCODE other than 0), or if no records are returned, or if more than one record is returned, or if there are syntax errors in the explanation string, then proceed as if no exp modifier was given.
|
72
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e22.example.com', 'mail.example.com', options)
|
73
|
+
expect(%i(fail)).to include(result.code)
|
74
|
+
expect(result.explanation).to eq('DEFAULT')
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'Ignore exp if DNS error. ' do
|
78
|
+
# If domain-spec is empty, or there are any DNS processing errors (any RCODE other than 0), or if no records are returned, or if more than one record is returned, or if there are syntax errors in the explanation string, then proceed as if no exp modifier was given.
|
79
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e21.example.com', 'mail.example.com', options)
|
80
|
+
expect(%i(fail)).to include(result.code)
|
81
|
+
expect(result.explanation).to eq('DEFAULT')
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'PermError if exp= domain-spec is empty. ' do
|
85
|
+
# Section 6.2/4 says, "If domain-spec is empty, or there are any DNS processing errors (any RCODE other than 0), or if no records are returned, or if more than one record is returned, or if there are syntax errors in the explanation string, then proceed as if no exp modifier was given." However, "if domain-spec is empty" conflicts with the grammar given for the exp modifier. This was reported as an erratum, and the solution chosen was to report explicit "exp=" as PermError, but ignore problems due to macro expansion, DNS, or invalid explanation string.
|
86
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e12.example.com', 'mail.example.com', options)
|
87
|
+
expect(%i(permerror)).to include(result.code)
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'Ignore exp if the explanation string has a syntax error. ' do
|
91
|
+
# If domain-spec is empty, or there are any DNS processing errors (any RCODE other than 0), or if no records are returned, or if more than one record is returned, or if there are syntax errors in the explanation string, then proceed as if no exp modifier was given.
|
92
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e13.example.com', 'mail.example.com', options)
|
93
|
+
expect(%i(fail)).to include(result.code)
|
94
|
+
expect(result.explanation).to eq('DEFAULT')
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'explanation = "exp" "=" domain-spec ' do
|
98
|
+
# A literal application of the grammar causes modifier syntax errors (except for macro syntax) to become unknown-modifier.
|
99
|
+
#
|
100
|
+
# modifier = explanation | redirect | unknown-modifier
|
101
|
+
#
|
102
|
+
# However, it is generally agreed, with precedent in other RFCs, that unknown-modifier should not be "greedy", and should not match known modifier names. There should have been explicit prose to this effect, and some has been proposed as an erratum.
|
103
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e16.example.com', 'mail.example.com', options)
|
104
|
+
expect(%i(permerror)).to include(result.code)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'exp= appears twice. ' do
|
108
|
+
# These two modifiers (exp,redirect) MUST NOT appear in a record more than once each. If they do, then check_host() exits with a result of "PermError".
|
109
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e14.example.com', 'mail.example.com', options)
|
110
|
+
expect(%i(permerror)).to include(result.code)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'redirect = "redirect" "=" domain-spec ' do
|
114
|
+
# Unlike for exp, there is no instruction to override the permerror for an empty domain-spec (which is invalid syntax).
|
115
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e18.example.com', 'mail.example.com', options)
|
116
|
+
expect(%i(permerror)).to include(result.code)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'redirect= appears twice. ' do
|
120
|
+
# These two modifiers (exp,redirect) MUST NOT appear in a record more than once each. If they do, then check_host() exits with a result of "PermError".
|
121
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e15.example.com', 'mail.example.com', options)
|
122
|
+
expect(%i(permerror)).to include(result.code)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'unknown-modifier = name "=" macro-string ' do
|
126
|
+
# Unknown modifiers must have valid macro syntax.
|
127
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e9.example.com', 'mail.example.com', options)
|
128
|
+
expect(%i(permerror)).to include(result.code)
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'Unknown modifiers do not modify the RFC SPF result. ' do
|
132
|
+
# Some implementations may have a leftover default= modifier from earlier drafts.
|
133
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e19.example.com', 'mail.example.com', options)
|
134
|
+
expect(%i(neutral)).to include(result.code)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'Unknown modifiers do not modify the RFC SPF result. ' do
|
138
|
+
# Some implementations may have a leftover default= modifier from earlier drafts.
|
139
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e20.example.com', 'mail.example.com', options)
|
140
|
+
expect(%i(neutral)).to include(result.code)
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'SPF explanation text is restricted to 7-bit ascii.' do
|
144
|
+
# Checking a possibly different code path for non-ascii chars.
|
145
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foobar@nonascii.example.com', 'hosed', options)
|
146
|
+
expect(%i(fail)).to include(result.code)
|
147
|
+
expect(result.explanation).to eq('DEFAULT')
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'Must ignore exp= if DNS returns more than one TXT record.' do
|
151
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foobar@tworecs.example.com', 'hosed', options)
|
152
|
+
expect(%i(fail)).to include(result.code)
|
153
|
+
expect(result.explanation).to eq('DEFAULT')
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'exp=nxdomain.tld ' do
|
157
|
+
# Non-existent exp= domains MUST NOT count against the void lookup limit. Implementations should lookup any exp record at most once after computing the result.
|
158
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e23.example.com', 'mail.example.com', options)
|
159
|
+
expect(%i(fail)).to include(result.code)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'redirect changes implicit domain ' do
|
163
|
+
result = Coppertone::SpfService.authenticate_email('192.0.2.2', 'bar@e24.example.com', 'e24.example.com', options)
|
164
|
+
expect(%i(pass)).to include(result.code)
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Test cases from implementation bugs' do
|
4
|
+
let(:zonefile) do
|
5
|
+
{ 'example.org' => [{ 'TXT' => 'v=spf1 mx redirect=_spf.example.com' }, { 'MX' => [10, 'smtp.example.org'] }, { 'MX' => [10, 'smtp1.example.com'] }], 'smtp.example.org' => [{ 'A' => '198.51.100.2' }, { 'AAAA' => '2001:db8:ff0:100::3' }], 'smtp1.example.com' => [{ 'A' => '192.0.2.26' }, { 'AAAA' => '2001:db8:ff0:200::2' }], '2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.F.F.0.8.B.D.0.1.0.0.2.ip6.arpa' => [{ 'PTR' => 'smtp6-v.fe.example.org' }], 'smtp6-v.fe.example.org' => [{ 'AAAA' => '2001:db8:ff0:100::2' }], '_spf.example.com' => [{ 'TXT' => 'v=spf1 ptr:fe.example.org ptr:sgp.example.com exp=_expspf.example.org -all' }], '_expspf.example.org' => [{ 'TXT' => 'Sender domain not allowed from this host. Please see http://www.openspf.org/Why?s=mfrom&id=%{S}&ip=%{C}&r=%{R}' }] }
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
|
9
|
+
let(:options) { { dns_client: dns_client } }
|
10
|
+
|
11
|
+
it 'Bytes vs str bug from pyspf.' do
|
12
|
+
# Pyspf failed with strict=2 only. Other implementations may ignore the strict parameter.
|
13
|
+
result = Coppertone::SpfService.authenticate_email('2001:db8:ff0:100::2', 'test@example.org', 'example.org', options)
|
14
|
+
expect(%i(pass)).to include(result.code)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Coppertone::Qualifier do
|
4
|
+
context '#find_by_text' do
|
5
|
+
{
|
6
|
+
'+' => Coppertone::Qualifier::PASS,
|
7
|
+
'-' => Coppertone::Qualifier::FAIL,
|
8
|
+
'~' => Coppertone::Qualifier::SOFTFAIL,
|
9
|
+
'?' => Coppertone::Qualifier::NEUTRAL
|
10
|
+
}.each do |k, v|
|
11
|
+
it "should map from #{k} to the correct value" do
|
12
|
+
expect(Coppertone::Qualifier.find_by_text(k)).to eq(v)
|
13
|
+
expect(v.text).to eq(k)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context '#default_qualifier' do
|
19
|
+
it 'should yield the correct default qualifier' do
|
20
|
+
expect(Coppertone::Qualifier.default_qualifier)
|
21
|
+
.to eq(Coppertone::Qualifier::PASS)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context '#qualifiers' do
|
26
|
+
it 'should have the correct contents' do
|
27
|
+
qualifiers = Coppertone::Qualifier.qualifiers
|
28
|
+
expect(qualifiers.size).to eq(4)
|
29
|
+
expect(qualifiers.include?(Coppertone::Qualifier::PASS)).to be(true)
|
30
|
+
expect(qualifiers.include?(Coppertone::Qualifier::FAIL)).to be(true)
|
31
|
+
expect(qualifiers.include?(Coppertone::Qualifier::SOFTFAIL)).to be(true)
|
32
|
+
expect(qualifiers.include?(Coppertone::Qualifier::NEUTRAL)).to be(true)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'result' do
|
37
|
+
{
|
38
|
+
Coppertone::Qualifier::PASS => Coppertone::Result::PASS,
|
39
|
+
Coppertone::Qualifier::FAIL => Coppertone::Result::FAIL,
|
40
|
+
Coppertone::Qualifier::SOFTFAIL => Coppertone::Result::SOFTFAIL,
|
41
|
+
Coppertone::Qualifier::NEUTRAL => Coppertone::Result::NEUTRAL
|
42
|
+
}.each do |k, v|
|
43
|
+
it "should have the correct result code for #{k.text}" do
|
44
|
+
expect(k.result_code).to eq(v)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should not allow creation of new qualifiers' do
|
50
|
+
expect do
|
51
|
+
Coppertone::Qualifier.new('a', Coppertone::Result::PASS)
|
52
|
+
end.to raise_error(NoMethodError)
|
53
|
+
end
|
54
|
+
end
|
data/spec/record_spec.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Coppertone::Record do
|
4
|
+
context '#record?' do
|
5
|
+
it 'should return a falsey value for nil' do
|
6
|
+
expect(Coppertone::Record.record?(nil)).to be_falsey
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should return a falsey value for text without the prefix' do
|
10
|
+
expect(Coppertone::Record.record?('not a record')).to be_falsey
|
11
|
+
expect(Coppertone::Record.record?('v=spf ~all')).to be_falsey
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should return a truthy value for text with the prefix' do
|
15
|
+
expect(Coppertone::Record.record?('v=spf1 ~all')).to be_truthy
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'parsing' do
|
20
|
+
it 'should return nil for nil' do
|
21
|
+
expect(Coppertone::Record.parse(nil)).to be_nil
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should return a nil for text without the prefix' do
|
25
|
+
expect(Coppertone::Record.parse('not a record')).to be_nil
|
26
|
+
expect(Coppertone::Record.parse('v=spf ~all')).to be_nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'parse simple mechanism records' do
|
30
|
+
record = Coppertone::Record.parse('v=spf1 ~all')
|
31
|
+
expect(record).not_to be_nil
|
32
|
+
expect(record.directives.size).to eq(1)
|
33
|
+
directive = record.directives.first
|
34
|
+
expect(directive.qualifier).to eq(Coppertone::Qualifier::SOFTFAIL)
|
35
|
+
expect(directive.mechanism).to eq(Coppertone::Mechanism::All.instance)
|
36
|
+
expect(record.modifiers).to be_empty
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'be case insensitive when parsing the version string' do
|
40
|
+
record = Coppertone::Record.parse('V=sPf1 ~all')
|
41
|
+
expect(record).not_to be_nil
|
42
|
+
expect(record.directives.size).to eq(1)
|
43
|
+
directive = record.directives.first
|
44
|
+
expect(directive.qualifier).to eq(Coppertone::Qualifier::SOFTFAIL)
|
45
|
+
expect(directive.mechanism).to eq(Coppertone::Mechanism::All.instance)
|
46
|
+
expect(record.modifiers).to be_empty
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should parse more complex records' do
|
50
|
+
record = Coppertone::Record.parse('v=spf1 mx -all exp=explain._spf.%{d}')
|
51
|
+
expect(record).not_to be_nil
|
52
|
+
expect(record.directives.size).to eq(2)
|
53
|
+
directive = record.directives.first
|
54
|
+
expect(directive.qualifier).to eq(Coppertone::Qualifier::PASS)
|
55
|
+
expect(directive.mechanism).to eq(Coppertone::Mechanism::MX.new(nil))
|
56
|
+
|
57
|
+
directive = record.directives.last
|
58
|
+
expect(directive.qualifier).to eq(Coppertone::Qualifier::FAIL)
|
59
|
+
expect(directive.mechanism).to eq(Coppertone::Mechanism::All.instance)
|
60
|
+
|
61
|
+
expect(record.modifiers.length).to eq(1)
|
62
|
+
modifier = record.modifiers.first
|
63
|
+
expect(modifier).to eq(Coppertone::Modifier::Exp.new('explain._spf.%{d}'))
|
64
|
+
|
65
|
+
expect(record.redirect).to be_nil
|
66
|
+
expect(record.exp)
|
67
|
+
.to eq(Coppertone::Modifier::Exp.new('explain._spf.%{d}'))
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should fail on more records with duplicate modifiers' do
|
71
|
+
bad_records = [
|
72
|
+
'v=spf1 mx -all exp=explain._spf.%{d} exp=other._spf.%{d}',
|
73
|
+
'v=spf1 mx redirect=gmail.com redirect=yahoo.com'
|
74
|
+
]
|
75
|
+
bad_records.each do |rec|
|
76
|
+
expect do
|
77
|
+
Coppertone::Record.parse(rec)
|
78
|
+
end.to raise_error(Coppertone::RecordParsingError)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should fail when mechanisms are separated by ctrl characters' do
|
83
|
+
expect do
|
84
|
+
Coppertone::Record.parse("v=spf1 a:ctrl.example.com\x0dptr -all")
|
85
|
+
end.to raise_error(Coppertone::RecordParsingError)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should fail when it contains spurious terms' do
|
89
|
+
expect do
|
90
|
+
Coppertone::Record.parse('v=spf1 ip4:1.2.3.4 -all moo')
|
91
|
+
end.to raise_error(Coppertone::RecordParsingError)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should fail the domain-spec is not syntactically valid' do
|
95
|
+
expect do
|
96
|
+
Coppertone::Record.parse('v=spf1 a:foo-bar')
|
97
|
+
end.to raise_error(Coppertone::RecordParsingError)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Coppertone::RequestContext do
|
4
|
+
it 'should have reasonable values by default' do
|
5
|
+
ctx = Coppertone::RequestContext.new
|
6
|
+
expect(ctx.dns_client).to_not be_nil
|
7
|
+
expect(ctx.dns_client.class).to eq(Coppertone::DNS::ResolvClient)
|
8
|
+
expect(ctx.message_locale).to eq('en')
|
9
|
+
expect(ctx.dns_lookups_per_mx_mechanism_limit).to eq(10)
|
10
|
+
expect(ctx.dns_lookups_per_ptr_mechanism_limit).to eq(10)
|
11
|
+
expect do
|
12
|
+
11.times { ctx.register_dns_lookup_term }
|
13
|
+
end.to raise_error(Coppertone::LimitExceededError)
|
14
|
+
expect do
|
15
|
+
3.times { ctx.register_void_dns_result }
|
16
|
+
end.to raise_error(Coppertone::LimitExceededError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should allow override of these values using options' do
|
20
|
+
dns_client = double
|
21
|
+
options = {
|
22
|
+
dns_client: dns_client,
|
23
|
+
message_locale: 'es',
|
24
|
+
terms_requiring_dns_lookup_limit: 20,
|
25
|
+
void_dns_result_limit: 4,
|
26
|
+
dns_lookups_per_mx_mechanism_limit: 18,
|
27
|
+
dns_lookups_per_ptr_mechanism_limit: 17
|
28
|
+
}
|
29
|
+
ctx = Coppertone::RequestContext.new(options)
|
30
|
+
expect(ctx.dns_client).to eq(dns_client)
|
31
|
+
expect(ctx.message_locale).to eq('es')
|
32
|
+
20.times { ctx.register_dns_lookup_term }
|
33
|
+
expect do
|
34
|
+
ctx.register_dns_lookup_term
|
35
|
+
end.to raise_error(Coppertone::LimitExceededError)
|
36
|
+
4.times { ctx.register_void_dns_result }
|
37
|
+
expect do
|
38
|
+
ctx.register_void_dns_result
|
39
|
+
end.to raise_error(Coppertone::LimitExceededError)
|
40
|
+
expect(ctx.dns_lookups_per_mx_mechanism_limit).to eq(18)
|
41
|
+
expect(ctx.dns_lookups_per_ptr_mechanism_limit).to eq(17)
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Coppertone::RequestCountLimiter do
|
4
|
+
let(:counter_description) { SecureRandom.hex(10) }
|
5
|
+
|
6
|
+
it 'should handle the basic case correctly' do
|
7
|
+
limiter = Coppertone::RequestCountLimiter.new(5, counter_description)
|
8
|
+
expect(limiter).to be_limited
|
9
|
+
expect(limiter.increment!).to eq(1)
|
10
|
+
expect(limiter.increment!).to eq(2)
|
11
|
+
expect(limiter.increment!(2)).to eq(4)
|
12
|
+
expect(limiter.increment!).to eq(5)
|
13
|
+
expect(limiter).not_to be_exceeded
|
14
|
+
expect { limiter.increment! }
|
15
|
+
.to raise_error Coppertone::LimitExceededError,
|
16
|
+
"Maximum #{counter_description} limit of 5 exceeded."
|
17
|
+
expect(limiter).to be_exceeded
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should not raise an error if no limit is specified' do
|
21
|
+
limiter = Coppertone::RequestCountLimiter.new
|
22
|
+
expect(limiter).not_to be_limited
|
23
|
+
expect(limiter.increment!).to eq(1)
|
24
|
+
expect(limiter.increment!(1000)).to eq(1001)
|
25
|
+
expect(limiter.count).to eq(1001)
|
26
|
+
expect(limiter).not_to be_exceeded
|
27
|
+
end
|
28
|
+
end
|
data/spec/result_spec.rb
ADDED