dmarc 0.1.0 → 0.2.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 +4 -4
- data/.document +3 -0
- data/.rspec +1 -0
- data/.travis.yml +7 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +12 -0
- data/Gemfile +9 -0
- data/{LICENSE → LICENSE.txt} +0 -0
- data/README.md +37 -13
- data/Rakefile +9 -0
- data/dmarc.gemspec +0 -3
- data/lib/dmarc.rb +1 -0
- data/lib/dmarc/exceptions.rb +19 -0
- data/lib/dmarc/parser.rb +124 -102
- data/lib/dmarc/record.rb +7 -9
- data/lib/dmarc/version.rb +1 -1
- data/spec/data/alexa.csv +500 -0
- data/spec/{lib/dmarc/parser_spec.rb → parser_spec.rb} +144 -17
- data/spec/{lib/dmarc/record_spec.rb → record_spec.rb} +27 -7
- data/spec/spec_helper.rb +8 -0
- data/tasks/alexa.rb +43 -0
- metadata +19 -50
@@ -1,22 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
1
2
|
require 'dmarc/parser'
|
2
|
-
require 'pp'
|
3
3
|
|
4
|
-
describe
|
4
|
+
describe Parser do
|
5
5
|
describe '#dmarc_uri' do
|
6
6
|
subject { described_class.new.dmarc_uri }
|
7
7
|
|
8
|
+
let(:uri) { 'mailto:user@example.org' }
|
9
|
+
|
8
10
|
it 'parses mailto URIs' do
|
9
|
-
uri = 'mailto:user@example.org'
|
10
11
|
expect(subject.parse(uri)).to eq(uri: uri)
|
11
12
|
end
|
12
13
|
|
13
14
|
it 'parses mailto URIs with size' do
|
14
|
-
uri = 'mailto:user@example.org'
|
15
15
|
expect(subject.parse(uri + '!20')).to eq(uri: uri, size: '20')
|
16
16
|
end
|
17
17
|
|
18
18
|
it 'parses mailto URIs with size and unit' do
|
19
|
-
uri = 'mailto:user@example.org'
|
20
19
|
expect(subject.parse(uri + '!20k')).to eq(uri: uri, size: '20', unit: 'k')
|
21
20
|
end
|
22
21
|
end
|
@@ -28,18 +27,30 @@ describe DMARC::Parser do
|
|
28
27
|
record = 'v=DMARC1;p=none'
|
29
28
|
expect(subject.parse record).to eq([
|
30
29
|
{v: 'DMARC1'},
|
31
|
-
{p: 'none'}
|
30
|
+
{p: 'none'}
|
32
31
|
])
|
33
32
|
end
|
34
33
|
|
35
34
|
it 'parses version, policy, and other tags' do
|
36
35
|
record = 'v=DMARC1;p=none;sp=reject;adkim=r;aspf=r'
|
37
|
-
expect(subject.parse
|
36
|
+
expect(subject.parse(record)).to eq([
|
38
37
|
{v: 'DMARC1'},
|
39
38
|
{p: 'none'},
|
40
39
|
{sp: 'reject'},
|
41
40
|
{adkim: 'r'},
|
42
|
-
{aspf: 'r'}
|
41
|
+
{aspf: 'r'}
|
42
|
+
])
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'parses version, policy, and rua' do
|
46
|
+
record = 'v=DMARC1;p=quarantine;rua=mailto:foo@example.com,mailto:bar@example.com'
|
47
|
+
expect(subject.parse record).to eq([
|
48
|
+
{v: 'DMARC1'},
|
49
|
+
{p: 'quarantine'},
|
50
|
+
{rua: [
|
51
|
+
{uri: 'mailto:foo@example.com'},
|
52
|
+
{uri: 'mailto:bar@example.com'}
|
53
|
+
]}
|
43
54
|
])
|
44
55
|
end
|
45
56
|
|
@@ -48,6 +59,25 @@ describe DMARC::Parser do
|
|
48
59
|
record2 = 'v = DMARC1 ; p = none ; sp = reject'
|
49
60
|
expect(subject.parse record1).to eq(subject.parse record2)
|
50
61
|
end
|
62
|
+
|
63
|
+
it "ignores unknown tags" do
|
64
|
+
record = 'v=DMARC1;p=none;foo=xxx;sp=reject;bar=xxx;adkim=r;aspf=r'
|
65
|
+
expect(subject.parse(record)).to eq([
|
66
|
+
{v: 'DMARC1'},
|
67
|
+
{p: 'none'},
|
68
|
+
{sp: 'reject'},
|
69
|
+
{adkim: 'r'},
|
70
|
+
{aspf: 'r'}
|
71
|
+
])
|
72
|
+
end
|
73
|
+
|
74
|
+
it "ignores syntax errors" do
|
75
|
+
record = 'v=DMARC1;p=none;sp;adkim=X;aspf='
|
76
|
+
expect(subject.parse(record)).to eq([
|
77
|
+
{v: 'DMARC1'},
|
78
|
+
{p: 'none'}
|
79
|
+
])
|
80
|
+
end
|
51
81
|
end
|
52
82
|
|
53
83
|
describe '#dmarc_version' do
|
@@ -116,10 +146,22 @@ describe DMARC::Parser do
|
|
116
146
|
it 'parses many URIs' do
|
117
147
|
expect(
|
118
148
|
subject.parse('rua = mailto:user1@example.org, mailto:user2@example.org')
|
119
|
-
).to eq(
|
120
|
-
|
121
|
-
|
122
|
-
|
149
|
+
).to eq(
|
150
|
+
rua: [
|
151
|
+
{uri: 'mailto:user1@example.org'},
|
152
|
+
{uri: 'mailto:user2@example.org'}
|
153
|
+
]
|
154
|
+
)
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'parses maximum report size' do
|
158
|
+
expect(subject.parse('rua = mailto:user1@example.com!20m')).to eq(
|
159
|
+
rua: {
|
160
|
+
uri: 'mailto:user1@example.com',
|
161
|
+
size: '20',
|
162
|
+
unit: 'm'
|
163
|
+
}
|
164
|
+
)
|
123
165
|
end
|
124
166
|
end
|
125
167
|
|
@@ -153,10 +195,40 @@ describe DMARC::Parser do
|
|
153
195
|
it 'parses many URIs' do
|
154
196
|
expect(
|
155
197
|
subject.parse('ruf = mailto:user1@example.org, mailto:user2@example.org')
|
156
|
-
).to eq(
|
157
|
-
|
158
|
-
|
159
|
-
|
198
|
+
).to eq(
|
199
|
+
ruf: [
|
200
|
+
{uri: 'mailto:user1@example.org'},
|
201
|
+
{uri: 'mailto:user2@example.org'}
|
202
|
+
]
|
203
|
+
)
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'parses maximum report size' do
|
207
|
+
expect(subject.parse('ruf = mailto:user1@example.com!20m')).to eq(
|
208
|
+
ruf: {
|
209
|
+
uri: 'mailto:user1@example.com',
|
210
|
+
size: '20',
|
211
|
+
unit: 'm'
|
212
|
+
}
|
213
|
+
)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
describe '#dmarc_fo' do
|
218
|
+
let(:fo) { described_class.new.dmarc_fo }
|
219
|
+
|
220
|
+
context 'one value' do
|
221
|
+
%w[0 1 d s].each do |value|
|
222
|
+
it "parses #{value}" do
|
223
|
+
expect(fo.parse("fo=#{value}")).to eq(fo: {fo_opt: value})
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'parses many values' do
|
229
|
+
expect(fo.parse('fo=0:1:d:s')).to eq(
|
230
|
+
fo: [{fo_opt: '0'}, {fo_opt: '1'}, {fo_opt: 'd'}, {fo_opt: 's'}]
|
231
|
+
)
|
160
232
|
end
|
161
233
|
end
|
162
234
|
|
@@ -227,5 +299,60 @@ describe DMARC::Parser do
|
|
227
299
|
expect(subject.parse 'aspf=s').to eq(subject.parse 'aspf = s')
|
228
300
|
end
|
229
301
|
end
|
230
|
-
end
|
231
302
|
|
303
|
+
describe Parser::Transform do
|
304
|
+
let(:tree) do
|
305
|
+
[
|
306
|
+
{v: 'DMARC1'},
|
307
|
+
{p: 'none'},
|
308
|
+
{pct: '100'},
|
309
|
+
{fo: [{fo_opt: '0'}, {fo_opt: '1'}, {fo_opt: 'd'}, {fo_opt: 's'}]}
|
310
|
+
]
|
311
|
+
end
|
312
|
+
|
313
|
+
subject { described_class.new.apply(tree) }
|
314
|
+
|
315
|
+
it "should coerce :p into a Symbol" do
|
316
|
+
expect(subject).to include(p: :none)
|
317
|
+
end
|
318
|
+
|
319
|
+
it "should flatten :fo options" do
|
320
|
+
expect(subject).to include(fo: %w[0 1 d s])
|
321
|
+
end
|
322
|
+
|
323
|
+
it "should coerce :pct into an Integer" do
|
324
|
+
expect(subject).to include(pct: 100)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
describe "#parse" do
|
329
|
+
let(:record) { "v=DMARC1;p=none;pct=100;fo=0:1:d:s" }
|
330
|
+
|
331
|
+
it "should combine all tags into one Hash" do
|
332
|
+
expect(subject.parse(record)).to eq(
|
333
|
+
v: 'DMARC1',
|
334
|
+
p: :none,
|
335
|
+
pct: 100,
|
336
|
+
fo: ['0', '1', 'd', 's']
|
337
|
+
)
|
338
|
+
end
|
339
|
+
|
340
|
+
end
|
341
|
+
|
342
|
+
context "Alexa Top 500", :gauntlet do
|
343
|
+
require 'csv'
|
344
|
+
|
345
|
+
path = File.expand_path('../data/alexa.csv', __FILE__)
|
346
|
+
csv = CSV.new(open(path), headers: false)
|
347
|
+
|
348
|
+
csv.each do |row|
|
349
|
+
rank, domain, dmarc = row
|
350
|
+
|
351
|
+
if dmarc
|
352
|
+
it "should parse #{dmarc.inspect}" do
|
353
|
+
expect { subject.parse(dmarc) }.to_not raise_error
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
require 'spec_helper'
|
1
2
|
require 'dmarc/record'
|
2
3
|
|
3
|
-
describe
|
4
|
+
describe Record do
|
4
5
|
context 'by default' do
|
5
6
|
it 'has a relaxed DKIM alignment' do
|
6
7
|
expect(subject.adkim).to eq('r')
|
@@ -29,11 +30,11 @@ describe DMARC::Record do
|
|
29
30
|
|
30
31
|
describe '#initialize' do
|
31
32
|
let(:parse_tree) do
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
{
|
34
|
+
v: 'DMARC1',
|
35
|
+
p: 'none',
|
36
|
+
adkim: 'r'
|
37
|
+
}
|
37
38
|
end
|
38
39
|
|
39
40
|
it 'assigns the fields to its properties' do
|
@@ -48,5 +49,24 @@ describe DMARC::Record do
|
|
48
49
|
expect(rec.sp).to eq('none')
|
49
50
|
end
|
50
51
|
end
|
51
|
-
end
|
52
52
|
|
53
|
+
describe '.from_txt' do
|
54
|
+
context 'with a valid record' do
|
55
|
+
it 'parse and returns a record' do
|
56
|
+
rec = described_class.from_txt('v=DMARC1; p=quarantine')
|
57
|
+
|
58
|
+
expect(rec).to be_a Record
|
59
|
+
expect(rec.p).to eq :quarantine
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'with an invalid record' do
|
64
|
+
it 'raises an InvalidRecord error' do
|
65
|
+
expect { described_class.from_txt('v=DMARC1; foo=bar') }.to raise_error do |error|
|
66
|
+
expect(error).to be_a InvalidRecord
|
67
|
+
expect(error.ascii_tree).to_not be_nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/tasks/alexa.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
##
|
2
|
+
# Adapted from gist: https://gist.github.com/zerothabhishek/3015666
|
3
|
+
# Orginal author: zerothabhishek
|
4
|
+
##
|
5
|
+
|
6
|
+
require 'nokogiri'
|
7
|
+
require 'open-uri'
|
8
|
+
require 'resolv'
|
9
|
+
require 'csv'
|
10
|
+
|
11
|
+
namespace :alexa do
|
12
|
+
desc 'Scrapes the Alexa Top 500 and updates spec/data/alexa.csv'
|
13
|
+
task :scrape do
|
14
|
+
resolver = Resolv::DNS.new
|
15
|
+
|
16
|
+
CSV.open("spec/data/alexa.csv","w") do |csv|
|
17
|
+
(0..19).each do |i|
|
18
|
+
url = "http://www.alexa.com/topsites/global;#{i} "
|
19
|
+
doc = Nokogiri::HTML(open(url))
|
20
|
+
|
21
|
+
doc.css(".site-listing").each do |li|
|
22
|
+
begin
|
23
|
+
site_name = li.css(".desc-container .desc-paragraph a")[0].content
|
24
|
+
site_rank = li.css(".count")[0].content
|
25
|
+
|
26
|
+
puts "Resolving #{site_name} ..."
|
27
|
+
dmarc = begin
|
28
|
+
resolver.getresource(
|
29
|
+
"_dmarc.#{site_name.downcase}",
|
30
|
+
Resolv::DNS::Resource::IN::TXT
|
31
|
+
).strings.join
|
32
|
+
rescue Resolv::ResolvError
|
33
|
+
end
|
34
|
+
|
35
|
+
csv << [site_rank, site_name, dmarc]
|
36
|
+
rescue => exception
|
37
|
+
warn exception.message
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dmarc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Davis Gallinghouse
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-10-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parslet
|
@@ -38,48 +38,6 @@ dependencies:
|
|
38
38
|
- - ~>
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rake
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ~>
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '10.0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ~>
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '10.0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: rspec
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ~>
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '2.8'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ~>
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '2.8'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: yard
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ~>
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0.8'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ~>
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0.8'
|
83
41
|
description: DMARC Record Parser
|
84
42
|
email:
|
85
43
|
- davis@trailofbits.com
|
@@ -87,18 +45,27 @@ executables: []
|
|
87
45
|
extensions: []
|
88
46
|
extra_rdoc_files: []
|
89
47
|
files:
|
48
|
+
- .document
|
90
49
|
- .gitignore
|
50
|
+
- .rspec
|
51
|
+
- .travis.yml
|
52
|
+
- .yardopts
|
53
|
+
- ChangeLog.md
|
91
54
|
- Gemfile
|
92
|
-
- LICENSE
|
55
|
+
- LICENSE.txt
|
93
56
|
- README.md
|
94
57
|
- Rakefile
|
95
58
|
- dmarc.gemspec
|
96
59
|
- lib/dmarc.rb
|
60
|
+
- lib/dmarc/exceptions.rb
|
97
61
|
- lib/dmarc/parser.rb
|
98
62
|
- lib/dmarc/record.rb
|
99
63
|
- lib/dmarc/version.rb
|
100
|
-
- spec/
|
101
|
-
- spec/
|
64
|
+
- spec/data/alexa.csv
|
65
|
+
- spec/parser_spec.rb
|
66
|
+
- spec/record_spec.rb
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
- tasks/alexa.rb
|
102
69
|
homepage: https://github.com/trailofbits/dmarc#readme
|
103
70
|
licenses:
|
104
71
|
- MIT
|
@@ -119,11 +86,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
86
|
version: '0'
|
120
87
|
requirements: []
|
121
88
|
rubyforge_project:
|
122
|
-
rubygems_version: 2.
|
89
|
+
rubygems_version: 2.1.11
|
123
90
|
signing_key:
|
124
91
|
specification_version: 4
|
125
92
|
summary: DMARC Record Parser
|
126
93
|
test_files:
|
127
|
-
- spec/
|
128
|
-
- spec/
|
94
|
+
- spec/data/alexa.csv
|
95
|
+
- spec/parser_spec.rb
|
96
|
+
- spec/record_spec.rb
|
97
|
+
- spec/spec_helper.rb
|
129
98
|
has_rdoc:
|