coppertone 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +34 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE +201 -0
  6. data/README.md +58 -0
  7. data/Rakefile +140 -0
  8. data/coppertone.gemspec +27 -0
  9. data/lib/coppertone/class_builder.rb +20 -0
  10. data/lib/coppertone/directive.rb +38 -0
  11. data/lib/coppertone/dns/error.rb +9 -0
  12. data/lib/coppertone/dns/mock_client.rb +106 -0
  13. data/lib/coppertone/dns/resolv_client.rb +110 -0
  14. data/lib/coppertone/dns.rb +3 -0
  15. data/lib/coppertone/domain_spec.rb +45 -0
  16. data/lib/coppertone/error.rb +29 -0
  17. data/lib/coppertone/ip_address_wrapper.rb +75 -0
  18. data/lib/coppertone/macro_context.rb +67 -0
  19. data/lib/coppertone/macro_string/macro_expand.rb +84 -0
  20. data/lib/coppertone/macro_string/macro_literal.rb +24 -0
  21. data/lib/coppertone/macro_string/macro_parser.rb +62 -0
  22. data/lib/coppertone/macro_string/macro_static_expand.rb +52 -0
  23. data/lib/coppertone/macro_string.rb +31 -0
  24. data/lib/coppertone/mechanism/a.rb +16 -0
  25. data/lib/coppertone/mechanism/all.rb +24 -0
  26. data/lib/coppertone/mechanism/cidr_parser.rb +14 -0
  27. data/lib/coppertone/mechanism/domain_spec_mechanism.rb +18 -0
  28. data/lib/coppertone/mechanism/domain_spec_optional.rb +46 -0
  29. data/lib/coppertone/mechanism/domain_spec_required.rb +37 -0
  30. data/lib/coppertone/mechanism/domain_spec_with_dual_cidr.rb +114 -0
  31. data/lib/coppertone/mechanism/exists.rb +14 -0
  32. data/lib/coppertone/mechanism/include.rb +18 -0
  33. data/lib/coppertone/mechanism/include_matcher.rb +34 -0
  34. data/lib/coppertone/mechanism/ip4.rb +13 -0
  35. data/lib/coppertone/mechanism/ip6.rb +13 -0
  36. data/lib/coppertone/mechanism/ip_mechanism.rb +48 -0
  37. data/lib/coppertone/mechanism/mx.rb +40 -0
  38. data/lib/coppertone/mechanism/ptr.rb +17 -0
  39. data/lib/coppertone/mechanism.rb +32 -0
  40. data/lib/coppertone/modifier/base.rb +24 -0
  41. data/lib/coppertone/modifier/exp.rb +34 -0
  42. data/lib/coppertone/modifier/redirect.rb +17 -0
  43. data/lib/coppertone/modifier/unknown.rb +16 -0
  44. data/lib/coppertone/modifier.rb +30 -0
  45. data/lib/coppertone/qualifier.rb +45 -0
  46. data/lib/coppertone/record.rb +86 -0
  47. data/lib/coppertone/record_evaluator.rb +63 -0
  48. data/lib/coppertone/record_finder.rb +34 -0
  49. data/lib/coppertone/request.rb +68 -0
  50. data/lib/coppertone/request_context.rb +67 -0
  51. data/lib/coppertone/request_count_limiter.rb +36 -0
  52. data/lib/coppertone/result.rb +50 -0
  53. data/lib/coppertone/sender_identity.rb +39 -0
  54. data/lib/coppertone/spf_service.rb +9 -0
  55. data/lib/coppertone/term.rb +13 -0
  56. data/lib/coppertone/utils/domain_utils.rb +59 -0
  57. data/lib/coppertone/utils/host_utils.rb +22 -0
  58. data/lib/coppertone/utils/ip_in_domain_checker.rb +53 -0
  59. data/lib/coppertone/utils/validated_domain_finder.rb +40 -0
  60. data/lib/coppertone/utils.rb +4 -0
  61. data/lib/coppertone/version.rb +3 -0
  62. data/lib/coppertone.rb +48 -0
  63. data/lib/resolv/dns/resource/in/spf.rb +15 -0
  64. data/spec/directive_spec.rb +41 -0
  65. data/spec/dns/resolv_client_spec.rb +307 -0
  66. data/spec/domain_spec_spec.rb +35 -0
  67. data/spec/ip_address_wrapper_spec.rb +67 -0
  68. data/spec/macro_context_spec.rb +69 -0
  69. data/spec/macro_string/macro_expand_spec.rb +79 -0
  70. data/spec/macro_string/macro_literal_spec.rb +27 -0
  71. data/spec/macro_string/macro_static_expand_spec.rb +67 -0
  72. data/spec/macro_string_spec.rb +20 -0
  73. data/spec/mechanism/a_spec.rb +198 -0
  74. data/spec/mechanism/all_spec.rb +22 -0
  75. data/spec/mechanism/exists_spec.rb +91 -0
  76. data/spec/mechanism/include_spec.rb +43 -0
  77. data/spec/mechanism/ip4_spec.rb +110 -0
  78. data/spec/mechanism/ip6_spec.rb +104 -0
  79. data/spec/mechanism/mx_spec.rb +51 -0
  80. data/spec/mechanism/ptr_spec.rb +43 -0
  81. data/spec/mechanism_spec.rb +4 -0
  82. data/spec/modifier_spec.rb +4 -0
  83. data/spec/open_spf/ALL_mechanism_syntax_spec.rb +38 -0
  84. data/spec/open_spf/A_mechanism_syntax_spec.rb +159 -0
  85. data/spec/open_spf/EXISTS_mechanism_syntax_spec.rb +46 -0
  86. data/spec/open_spf/IP4_mechanism_syntax_spec.rb +59 -0
  87. data/spec/open_spf/IP6_mechanism_syntax_spec.rb +60 -0
  88. data/spec/open_spf/Include_mechanism_semantics_and_syntax_spec.rb +56 -0
  89. data/spec/open_spf/Initial_processing_spec.rb +77 -0
  90. data/spec/open_spf/MX_mechanism_syntax_spec.rb +119 -0
  91. data/spec/open_spf/Macro_expansion_rules_spec.rb +154 -0
  92. data/spec/open_spf/PTR_mechanism_syntax_spec.rb +42 -0
  93. data/spec/open_spf/Processing_limits_spec.rb +72 -0
  94. data/spec/open_spf/Record_evaluation_spec.rb +75 -0
  95. data/spec/open_spf/Record_lookup_spec.rb +48 -0
  96. data/spec/open_spf/Selecting_records_spec.rb +61 -0
  97. data/spec/open_spf/Semantics_of_exp_and_other_modifiers_spec.rb +167 -0
  98. data/spec/open_spf/Test_cases_from_implementation_bugs_spec.rb +17 -0
  99. data/spec/qualifier_spec.rb +54 -0
  100. data/spec/record_evaluator_spec.rb +4 -0
  101. data/spec/record_finder_spec.rb +4 -0
  102. data/spec/record_spec.rb +100 -0
  103. data/spec/request_context_spec.rb +43 -0
  104. data/spec/request_count_limiter_spec.rb +28 -0
  105. data/spec/result_spec.rb +4 -0
  106. data/spec/rfc7208-tests.yml +2548 -0
  107. data/spec/sender_identity_spec.rb +69 -0
  108. data/spec/spec_helper.rb +8 -0
  109. data/spec/term_spec.rb +38 -0
  110. data/spec/utils/domain_utils_spec.rb +60 -0
  111. data/spec/utils/host_utils_spec.rb +32 -0
  112. data/spec/utils/ip_in_domain_checker_spec.rb +4 -0
  113. data/spec/utils/validated_domain_finder_spec.rb +4 -0
  114. 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
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe Coppertone::RecordEvaluator do
4
+ end
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe Coppertone::RecordFinder do
4
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe Coppertone::Result do
4
+ end