dmarc 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,22 +1,21 @@
1
+ require 'spec_helper'
1
2
  require 'dmarc/parser'
2
- require 'pp'
3
3
 
4
- describe DMARC::Parser do
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 record).to eq([
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
- {rua: {uri: 'mailto:user1@example.org'}},
121
- {rua: {uri: 'mailto:user2@example.org'}},
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
- {ruf: {uri: 'mailto:user1@example.org'}},
158
- {ruf: {uri: 'mailto:user2@example.org'}},
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 DMARC::Record do
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
- {v: 'DMARC1'},
34
- {p: 'none'},
35
- {adkim: 'r'},
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
@@ -0,0 +1,8 @@
1
+ require 'rspec'
2
+ require 'dmarc'
3
+
4
+ include DMARC
5
+
6
+ RSpec.configure do |specs|
7
+ specs.filter_run_excluding :gauntlet
8
+ end
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.1.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-04-25 00:00:00.000000000 Z
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/lib/dmarc/parser_spec.rb
101
- - spec/lib/dmarc/record_spec.rb
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.0.14
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/lib/dmarc/parser_spec.rb
128
- - spec/lib/dmarc/record_spec.rb
94
+ - spec/data/alexa.csv
95
+ - spec/parser_spec.rb
96
+ - spec/record_spec.rb
97
+ - spec/spec_helper.rb
129
98
  has_rdoc: