dmarcator 0.1.0
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 +7 -0
- data/ChangeLog.md +55 -0
- data/LICENSE.txt +21 -0
- data/README.md +137 -0
- data/lib/dmarcator/exceptions.rb +6 -0
- data/lib/dmarcator/parser.rb +221 -0
- data/lib/dmarcator/query.rb +63 -0
- data/lib/dmarcator/record.rb +393 -0
- data/lib/dmarcator/uri.rb +114 -0
- data/lib/dmarcator/version.rb +4 -0
- data/lib/dmarcator.rb +5 -0
- data/spec/data/alexa.csv +500 -0
- data/spec/dmarcator_spec.rb +62 -0
- data/spec/parser_spec.rb +413 -0
- data/spec/record_spec.rb +229 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/uri_spec.rb +141 -0
- metadata +88 -0
data/spec/parser_spec.rb
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'dmarcator/parser'
|
|
3
|
+
|
|
4
|
+
describe Parser do
|
|
5
|
+
describe "rules" do
|
|
6
|
+
describe 'dmarc_uri' do
|
|
7
|
+
subject { described_class.new.dmarc_uri }
|
|
8
|
+
|
|
9
|
+
let(:uri) { 'mailto:user@example.org' }
|
|
10
|
+
|
|
11
|
+
it 'parses mailto URIs' do
|
|
12
|
+
expect(subject.parse(uri)).to eq(uri: uri)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'parses mailto URIs with size' do
|
|
16
|
+
expect(subject.parse(uri + '!20')).to eq(uri: uri, size: '20')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'parses mailto URIs with size and unit' do
|
|
20
|
+
expect(subject.parse(uri + '!20k')).to eq(uri: uri, size: '20', unit: 'k')
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe 'dmarc_record' do
|
|
25
|
+
subject { described_class.new.dmarc_record }
|
|
26
|
+
|
|
27
|
+
it 'parses version and policy' do
|
|
28
|
+
record = 'v=DMARC1;p=none'
|
|
29
|
+
expect(subject.parse record).to eq([
|
|
30
|
+
{v: 'DMARC1'},
|
|
31
|
+
{p: 'none'}
|
|
32
|
+
])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'parses version, policy, and other tags' do
|
|
36
|
+
record = 'v=DMARC1;p=none;sp=reject;adkim=r;aspf=r'
|
|
37
|
+
expect(subject.parse(record)).to eq([
|
|
38
|
+
{v: 'DMARC1'},
|
|
39
|
+
{p: 'none'},
|
|
40
|
+
{sp: 'reject'},
|
|
41
|
+
{adkim: 'r'},
|
|
42
|
+
{aspf: 'r'}
|
|
43
|
+
])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'parses version, policy, and rua' do
|
|
47
|
+
record = 'v=DMARC1;p=quarantine;rua=mailto:foo@example.com,mailto:bar@example.com'
|
|
48
|
+
expect(subject.parse record).to eq([
|
|
49
|
+
{v: 'DMARC1'},
|
|
50
|
+
{p: 'quarantine'},
|
|
51
|
+
{rua: [
|
|
52
|
+
{uri: 'mailto:foo@example.com'},
|
|
53
|
+
{uri: 'mailto:bar@example.com'}
|
|
54
|
+
]}
|
|
55
|
+
])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'ignores order of tags' do
|
|
59
|
+
record = 'v=DMARC1; rua=mailto:foo@example.com; p=none'
|
|
60
|
+
|
|
61
|
+
expect(subject.parse(record)).to eq([
|
|
62
|
+
{v: 'DMARC1'},
|
|
63
|
+
{rua: {uri: 'mailto:foo@example.com'}},
|
|
64
|
+
{p: 'none'}
|
|
65
|
+
])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'ignores spacing' do
|
|
69
|
+
record1 = 'v=DMARC1;p=none;sp=reject'
|
|
70
|
+
record2 = 'v = DMARC1 ; p = none ; sp = reject'
|
|
71
|
+
expect(subject.parse record1).to eq(subject.parse record2)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'ignores trailing separators' do
|
|
75
|
+
record = 'v=DMARC1; p=none; '
|
|
76
|
+
|
|
77
|
+
expect(subject.parse(record)).to eq([
|
|
78
|
+
{v: 'DMARC1'},
|
|
79
|
+
{p: 'none'}
|
|
80
|
+
])
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'ignores trailing spacing without final separator' do
|
|
84
|
+
record = 'v=DMARC1; p=none '
|
|
85
|
+
|
|
86
|
+
expect(subject.parse(record)).to eq([
|
|
87
|
+
{v: 'DMARC1'},
|
|
88
|
+
{p: 'none'}
|
|
89
|
+
])
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "ignores unknown tags" do
|
|
93
|
+
record = 'v=DMARC1;p=none;foo=xxx;sp=reject;bar=xxx;adkim=r;aspf=r'
|
|
94
|
+
expect(subject.parse(record)).to eq([
|
|
95
|
+
{v: 'DMARC1'},
|
|
96
|
+
{p: 'none'},
|
|
97
|
+
{sp: 'reject'},
|
|
98
|
+
{adkim: 'r'},
|
|
99
|
+
{aspf: 'r'}
|
|
100
|
+
])
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "ignores syntax errors" do
|
|
104
|
+
record = 'v=DMARC1;p=none;sp;adkim=X;aspf='
|
|
105
|
+
expect(subject.parse(record)).to eq([
|
|
106
|
+
{v: 'DMARC1'},
|
|
107
|
+
{p: 'none'}
|
|
108
|
+
])
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
describe 'dmarc_version' do
|
|
113
|
+
subject { described_class.new.dmarc_version }
|
|
114
|
+
|
|
115
|
+
it 'parses DMARC versions' do
|
|
116
|
+
expect(subject.parse('v=DMARC1')).to eq(v: 'DMARC1')
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'ignores spacing' do
|
|
120
|
+
expect(subject.parse 'v = DMARC1').to eq(subject.parse 'v=DMARC1')
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
describe 'dmarc_request' do
|
|
125
|
+
subject { described_class.new.dmarc_request }
|
|
126
|
+
|
|
127
|
+
it 'parses "none" requests' do
|
|
128
|
+
expect(subject.parse('p=none')).to eq(p: 'none')
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'parses quarantine requests' do
|
|
132
|
+
expect(subject.parse('p=quarantine')).to eq(p: 'quarantine')
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'parses reject requests' do
|
|
136
|
+
expect(subject.parse('p=reject')).to eq(p: 'reject')
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'ingores spacing' do
|
|
140
|
+
expect(subject.parse 'p=none').to eq(subject.parse 'p = none')
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
describe 'dmarc_srequest' do
|
|
145
|
+
subject { described_class.new.dmarc_srequest }
|
|
146
|
+
|
|
147
|
+
it 'parses "none" requests' do
|
|
148
|
+
expect(subject.parse('sp=none')).to eq(sp: 'none')
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it 'parses quarantine requests' do
|
|
152
|
+
expect(subject.parse('sp=quarantine')).to eq(sp: 'quarantine')
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'parses reject requests' do
|
|
156
|
+
expect(subject.parse('sp=reject')).to eq(sp: 'reject')
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'ignores spacing' do
|
|
160
|
+
expect(subject.parse 'sp=none').to eq(subject.parse 'sp = none')
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
describe 'dmarc_auri' do
|
|
165
|
+
subject { described_class.new.dmarc_auri }
|
|
166
|
+
|
|
167
|
+
it 'parses one URI' do
|
|
168
|
+
expect(subject.parse('rua=mailto:user@example.org')).to eq(
|
|
169
|
+
rua: {
|
|
170
|
+
uri: 'mailto:user@example.org'
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it 'parses many URIs' do
|
|
176
|
+
expect(
|
|
177
|
+
subject.parse('rua = mailto:user1@example.org, mailto:user2@example.org')
|
|
178
|
+
).to eq(
|
|
179
|
+
rua: [
|
|
180
|
+
{uri: 'mailto:user1@example.org'},
|
|
181
|
+
{uri: 'mailto:user2@example.org'}
|
|
182
|
+
]
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it 'parses maximum report size' do
|
|
187
|
+
expect(subject.parse('rua = mailto:user1@example.com!20m')).to eq(
|
|
188
|
+
rua: {
|
|
189
|
+
uri: 'mailto:user1@example.com',
|
|
190
|
+
size: '20',
|
|
191
|
+
unit: 'm'
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
describe 'dmarc_ainterval' do
|
|
198
|
+
subject { described_class.new.dmarc_ainterval }
|
|
199
|
+
|
|
200
|
+
it 'parses a one digit interval' do
|
|
201
|
+
expect(subject.parse('ri=1')).to eq(ri: '1')
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it 'parses a many digit interval' do
|
|
205
|
+
expect(subject.parse('ri=86400')).to eq(ri: '86400')
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it 'ignores spacing' do
|
|
209
|
+
expect(subject.parse 'ri = 86400').to eq(subject.parse 'ri=86400')
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
describe 'dmarc_furi' do
|
|
214
|
+
subject { described_class.new.dmarc_furi }
|
|
215
|
+
|
|
216
|
+
it 'parses one URI' do
|
|
217
|
+
expect(subject.parse('ruf=mailto:user@example.org')).to eq(
|
|
218
|
+
ruf: {
|
|
219
|
+
uri: 'mailto:user@example.org'
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it 'parses many URIs' do
|
|
225
|
+
expect(
|
|
226
|
+
subject.parse('ruf = mailto:user1@example.org, mailto:user2@example.org')
|
|
227
|
+
).to eq(
|
|
228
|
+
ruf: [
|
|
229
|
+
{uri: 'mailto:user1@example.org'},
|
|
230
|
+
{uri: 'mailto:user2@example.org'}
|
|
231
|
+
]
|
|
232
|
+
)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it 'parses maximum report size' do
|
|
236
|
+
expect(subject.parse('ruf = mailto:user1@example.com!20m')).to eq(
|
|
237
|
+
ruf: {
|
|
238
|
+
uri: 'mailto:user1@example.com',
|
|
239
|
+
size: '20',
|
|
240
|
+
unit: 'm'
|
|
241
|
+
}
|
|
242
|
+
)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
describe 'dmarc_fo' do
|
|
247
|
+
let(:fo) { described_class.new.dmarc_fo }
|
|
248
|
+
|
|
249
|
+
context 'one value' do
|
|
250
|
+
%w[0 1 d s].each do |value|
|
|
251
|
+
it "parses #{value}" do
|
|
252
|
+
expect(fo.parse("fo=#{value}")).to eq(fo: {fo_opt: value})
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
it 'parses many values' do
|
|
258
|
+
expect(fo.parse('fo=0:1:d:s')).to eq(
|
|
259
|
+
fo: [{fo_opt: '0'}, {fo_opt: '1'}, {fo_opt: 'd'}, {fo_opt: 's'}]
|
|
260
|
+
)
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
describe 'dmarc_rfmt' do
|
|
265
|
+
subject { described_class.new.dmarc_rfmt }
|
|
266
|
+
|
|
267
|
+
it 'parses afrf format' do
|
|
268
|
+
expect(subject.parse('rf=afrf')).to eq(rf: 'afrf')
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
it 'parses iodef format' do
|
|
272
|
+
expect(subject.parse('rf=iodef')).to eq(rf: 'iodef')
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
it 'ignores spacing' do
|
|
276
|
+
expect(subject.parse 'rf=iodef').to eq(subject.parse 'rf = iodef')
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
describe 'dmarc_percent' do
|
|
281
|
+
subject { described_class.new.dmarc_percent }
|
|
282
|
+
|
|
283
|
+
it 'parses a one-digit percent' do
|
|
284
|
+
expect(subject.parse('pct=1')).to eq(pct: '1')
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
it 'parses a two-digit percent' do
|
|
288
|
+
expect(subject.parse('pct=10')).to eq(pct: '10')
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
it 'parses a three-digit percent' do
|
|
292
|
+
expect(subject.parse('pct=100')).to eq(pct: '100')
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
it 'ignores spacing' do
|
|
296
|
+
expect(subject.parse 'pct = 100').to eq(subject.parse 'pct=100')
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
describe 'dmarc_adkim' do
|
|
301
|
+
subject { described_class.new.dmarc_adkim }
|
|
302
|
+
|
|
303
|
+
it 'parses a relaxed DKIM policy' do
|
|
304
|
+
expect(subject.parse('adkim=r')).to eq(adkim: 'r')
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
it 'parses a strict DKIM policy' do
|
|
308
|
+
expect(subject.parse('adkim=s')).to eq(adkim: 's')
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
it 'ignores spacing' do
|
|
312
|
+
expect(subject.parse 'adkim=r').to eq(subject.parse 'adkim = r')
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
describe 'dmarc_aspf' do
|
|
317
|
+
subject { described_class.new.dmarc_aspf }
|
|
318
|
+
|
|
319
|
+
it 'parses a relaxed SPF policy' do
|
|
320
|
+
expect(subject.parse('aspf=r')).to eq(aspf: 'r')
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
it 'parses a strict SPF policy' do
|
|
324
|
+
expect(subject.parse('aspf=s')).to eq(aspf: 's')
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
it 'ignores spacing' do
|
|
328
|
+
expect(subject.parse 'aspf=s').to eq(subject.parse 'aspf = s')
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
describe Parser::Transform do
|
|
334
|
+
let(:tree) do
|
|
335
|
+
[
|
|
336
|
+
{v: 'DMARC1'},
|
|
337
|
+
{p: 'none'},
|
|
338
|
+
{sp: 'reject'},
|
|
339
|
+
{pct: '100'},
|
|
340
|
+
{adkim: 'r'},
|
|
341
|
+
{aspf: 'r'},
|
|
342
|
+
{fo: [{fo_opt: '0'}, {fo_opt: '1'}, {fo_opt: 'd'}, {fo_opt: 's'}]},
|
|
343
|
+
{rua: {uri: "mailto:d@rua.agari.com"}}
|
|
344
|
+
]
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
subject { described_class.new.apply(tree) }
|
|
348
|
+
|
|
349
|
+
it "should coerce :v into a Symbol" do
|
|
350
|
+
expect(subject).to include(v: :DMARC1)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
it "should coerce :p into a Symbol" do
|
|
354
|
+
expect(subject).to include(p: :none)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
it "should coerce :sp into a Symbol" do
|
|
358
|
+
expect(subject).to include(sp: :reject)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
it "should flatten :fo options" do
|
|
362
|
+
expect(subject).to include(fo: %w[0 1 d s])
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
it "should coerce :pct into an Integer" do
|
|
366
|
+
expect(subject).to include(pct: 100)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
it "should coerce :adkim into a Symbol" do
|
|
370
|
+
expect(subject).to include(adkim: :r)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
it "should coerce :aspf into a Symbol" do
|
|
374
|
+
expect(subject).to include(aspf: :r)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
it "should convert {uri: ...} to Uri objects" do
|
|
378
|
+
expect(subject).to include(rua: [Uri.new(URI("mailto:d@rua.agari.com"))])
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
describe ".parse" do
|
|
383
|
+
subject { described_class }
|
|
384
|
+
|
|
385
|
+
let(:record) { "v=DMARC1;p=none;pct=100;fo=0:1:d:s" }
|
|
386
|
+
|
|
387
|
+
it "should combine all tags into one Hash" do
|
|
388
|
+
expect(subject.parse(record)).to eq(
|
|
389
|
+
v: :DMARC1,
|
|
390
|
+
p: :none,
|
|
391
|
+
pct: 100,
|
|
392
|
+
fo: ['0', '1', 'd', 's']
|
|
393
|
+
)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
context "Alexa Top 500", :gauntlet do
|
|
397
|
+
require 'csv'
|
|
398
|
+
|
|
399
|
+
path = File.expand_path('../data/alexa.csv', __FILE__)
|
|
400
|
+
csv = CSV.new(open(path), headers: false)
|
|
401
|
+
|
|
402
|
+
csv.each do |row|
|
|
403
|
+
rank, domain, dmarc = row
|
|
404
|
+
|
|
405
|
+
if dmarc
|
|
406
|
+
it "should parse #{dmarc.inspect}" do
|
|
407
|
+
expect { subject.parse(dmarc) }.to_not raise_error
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
end
|
data/spec/record_spec.rb
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'dmarcator/record'
|
|
3
|
+
|
|
4
|
+
describe Record do
|
|
5
|
+
describe '#initialize' do
|
|
6
|
+
let(:attributes) do
|
|
7
|
+
{
|
|
8
|
+
v: :DMARC1,
|
|
9
|
+
p: :none,
|
|
10
|
+
adkim: :r
|
|
11
|
+
}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
subject { described_class.new(attributes) }
|
|
15
|
+
|
|
16
|
+
it 'assigns the fields to its properties' do
|
|
17
|
+
expect(subject.v).to be :DMARC1
|
|
18
|
+
expect(subject.p).to be :none
|
|
19
|
+
expect(subject.adkim).to be :r
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'gives "sp" the same value as "p" if undefined' do
|
|
23
|
+
expect(subject.sp).to be :none
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context 'with default values' do
|
|
28
|
+
subject { described_class.new(v: :DMARC1) }
|
|
29
|
+
|
|
30
|
+
describe "#adkim" do
|
|
31
|
+
it "should return :r" do
|
|
32
|
+
expect(subject.adkim).to be == :r
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe "#aspf" do
|
|
37
|
+
it "should return :r" do
|
|
38
|
+
expect(subject.aspf).to be == :r
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "#fo" do
|
|
43
|
+
it "should return ['0']" do
|
|
44
|
+
expect(subject.fo).to be == ['0']
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe "#pct" do
|
|
49
|
+
it "should return 100" do
|
|
50
|
+
expect(subject.pct).to be == 100
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe "#rf" do
|
|
55
|
+
it "should return afrf" do
|
|
56
|
+
expect(subject.rf).to be == :afrf
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe "#ri" do
|
|
61
|
+
it "should return 86400" do
|
|
62
|
+
expect(subject.ri).to be == 86400
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe '.parse' do
|
|
68
|
+
subject { described_class }
|
|
69
|
+
|
|
70
|
+
context 'with a valid record' do
|
|
71
|
+
it 'parse and returns a record' do
|
|
72
|
+
rec = subject.parse('v=DMARC1; p=quarantine')
|
|
73
|
+
|
|
74
|
+
expect(rec).to be_a Record
|
|
75
|
+
expect(rec.p).to eq :quarantine
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
context 'with an invalid record' do
|
|
80
|
+
it 'raises an InvalidRecord error' do
|
|
81
|
+
expect {
|
|
82
|
+
subject.parse('v=XXXXXXXXXX')
|
|
83
|
+
}.to raise_error(InvalidRecord)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'raises when the p tag is missing' do
|
|
87
|
+
expect {
|
|
88
|
+
subject.parse('v=DMARC1')
|
|
89
|
+
}.to raise_error(InvalidRecord)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'raises when pct is out of range' do
|
|
93
|
+
expect {
|
|
94
|
+
subject.parse('v=DMARC1; p=none; pct=999')
|
|
95
|
+
}.to raise_error(InvalidRecord)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'raises when tags are duplicated' do
|
|
99
|
+
expect {
|
|
100
|
+
subject.parse('v=DMARC1; p=none; p=reject')
|
|
101
|
+
}.to raise_error(InvalidRecord)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
context 'with a non-mailto rua URI' do
|
|
106
|
+
it 'ignores the invalid rua tag' do
|
|
107
|
+
rec = subject.parse('v=DMARC1; p=none; rua=http://169.254.169.254/?x=1')
|
|
108
|
+
|
|
109
|
+
expect(rec).to be_a Record
|
|
110
|
+
expect(rec.rua).to be_nil
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
describe ".query" do
|
|
116
|
+
subject { described_class }
|
|
117
|
+
|
|
118
|
+
context "when the domain has a DMARC record" do
|
|
119
|
+
it "queries and parses the DMARC record" do
|
|
120
|
+
expect(DMARCator).to receive(:query)
|
|
121
|
+
.with("google.com", anything)
|
|
122
|
+
.and_return("v=DMARC1; p=reject")
|
|
123
|
+
|
|
124
|
+
record = subject.query("google.com")
|
|
125
|
+
|
|
126
|
+
expect(record).to be_kind_of(Record)
|
|
127
|
+
expect(record.v).to be == :DMARC1
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
context "when the domain has no DMARC record" do
|
|
132
|
+
it "returns nil" do
|
|
133
|
+
expect(DMARCator).to receive(:query)
|
|
134
|
+
.with("foobar.heythere", anything)
|
|
135
|
+
.and_return(nil)
|
|
136
|
+
|
|
137
|
+
expect(subject.query('foobar.heythere')).to be_nil
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
shared_examples "question mark method" do |field,value|
|
|
143
|
+
describe "#{field}?" do
|
|
144
|
+
context "when #{field} is set" do
|
|
145
|
+
subject do
|
|
146
|
+
described_class.new(v: :DMARC1, field => value)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it { expect(subject.send(:"#{field}?")).to be(true) }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
context "when #{field} is omitted" do
|
|
153
|
+
subject { described_class.new(v: :DMARC1) }
|
|
154
|
+
|
|
155
|
+
it { expect(subject.send(:"#{field}?")).to be(false) }
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
include_examples "question mark method", :adkim, :r
|
|
161
|
+
include_examples "question mark method", :aspf, :r
|
|
162
|
+
include_examples "question mark method", :fo, %w[0 1 d]
|
|
163
|
+
include_examples "question mark method", :p, :reject
|
|
164
|
+
include_examples "question mark method", :pct, 100
|
|
165
|
+
include_examples "question mark method", :rf, :afrf
|
|
166
|
+
include_examples "question mark method", :ri, 86400
|
|
167
|
+
include_examples "question mark method", :rua, [URI("mailto:bob@example.com")]
|
|
168
|
+
include_examples "question mark method", :ruf, [URI("mailto:bob@example.com")]
|
|
169
|
+
include_examples "question mark method", :sp, :reject
|
|
170
|
+
|
|
171
|
+
describe "#sp?" do
|
|
172
|
+
context "when sp is set" do
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
describe "#to_h" do
|
|
177
|
+
let(:v) { :DMARC1 }
|
|
178
|
+
let(:p) { :reject }
|
|
179
|
+
let(:rua) { [URI.parse('mailto:d@rua.agari.com')] }
|
|
180
|
+
let(:ruf) { [URI.parse('mailto:d@rua.agari.com')] }
|
|
181
|
+
let(:fo) { %w[0 1 d] }
|
|
182
|
+
|
|
183
|
+
subject do
|
|
184
|
+
described_class.new(
|
|
185
|
+
v: v,
|
|
186
|
+
p: p,
|
|
187
|
+
rua: rua,
|
|
188
|
+
ruf: ruf,
|
|
189
|
+
fo: fo
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it "should return a Hash" do
|
|
194
|
+
expect(subject.to_h).to be_kind_of(Hash)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it "should only include fields that have values" do
|
|
198
|
+
expect(subject.to_h).to be == {
|
|
199
|
+
v: v,
|
|
200
|
+
p: p,
|
|
201
|
+
rua: rua,
|
|
202
|
+
ruf: ruf,
|
|
203
|
+
fo: fo
|
|
204
|
+
}
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
describe "#to_s" do
|
|
209
|
+
let(:v) { :DMARC1 }
|
|
210
|
+
let(:p) { :reject }
|
|
211
|
+
let(:rua) { [URI.parse('mailto:d@rua.agari.com')] }
|
|
212
|
+
let(:ruf) { [URI.parse('mailto:d@rua.agari.com')] }
|
|
213
|
+
let(:fo) { %w[0 1 d] }
|
|
214
|
+
|
|
215
|
+
subject do
|
|
216
|
+
described_class.new(
|
|
217
|
+
v: v,
|
|
218
|
+
p: p,
|
|
219
|
+
rua: rua,
|
|
220
|
+
ruf: ruf,
|
|
221
|
+
fo: fo
|
|
222
|
+
)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it "should convert the record to a String" do
|
|
226
|
+
expect(subject.to_s).to be == "v=#{v}; p=#{p}; rua=#{rua[0]}; ruf=#{ruf[0]}; fo=#{fo[0]}:#{fo[1]}:#{fo[2]}"
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|