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.
@@ -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: