dmarc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3b3ddde1f4d2db595acab01a63352cb7a62e9cdf
4
+ data.tar.gz: 8199be76eb510e8693dc45fe296227ec4a00f9e4
5
+ SHA512:
6
+ metadata.gz: 0a1762477121db2b245d80c5929432850e68727043d019bd272a5fabbe26d766005e25bbc28e872fcfafcd03b7b1e18ec899d8de447b7630310e69cbaab8ae9a
7
+ data.tar.gz: aaf8c594a50a80c593976dad9d9d532e3abbae5fa7e1d42aa0c2743810588e16e156496650872698cecad50959f7c2b9b22f67e238ee7a56cafff7400a82d9fd
@@ -0,0 +1,19 @@
1
+ Gemfile.lock
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ coverage
7
+ InstalledFiles
8
+ lib/bundler/man
9
+ pkg
10
+ rdoc
11
+ spec/reports
12
+ test/tmp
13
+ test/version_tmp
14
+ tmp
15
+
16
+ # YARD artifacts
17
+ .yardoc
18
+ _yardoc
19
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Trail of Bits
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ dmarc
2
+ =====
3
+
4
+ [DMARC](http://tools.ietf.org/html/draft-kucherawy-dmarc-base-02) is a
5
+ technical specification intended to solve a couple of long-standing email
6
+ authentication problems. DMARC policies are described in DMARC "records," which
7
+ are stored as DNS TXT records on a subdomain. This library contains a parser
8
+ for DMARC records.
9
+
10
+ Usage
11
+ -----
12
+
13
+ ```ruby
14
+ require 'dmarc/record'
15
+ record = DMARC::Record.from_txt(txt) # txt is a DNS TXT record containing the DMARC policy
16
+ ```
17
+
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+
5
+ begin
6
+ require 'bundler/setup'
7
+ rescue LoadError => e
8
+ warn e.message
9
+ warn "Run `gem install bundler` to install Bundler."
10
+ exit -1
11
+ end
12
+
13
+ require 'rake'
14
+ require 'bundler/gem_tasks'
15
+
16
+ require 'rspec/core/rake_task'
17
+ RSpec::Core::RakeTask.new
18
+
19
+ task :test => :spec
20
+ task :default => :spec
21
+
22
+ require 'yard'
23
+ YARD::Rake::YardocTask.new
24
+ task :doc => :yard
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dmarc/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "dmarc"
8
+ gem.version = DMARC::VERSION
9
+ gem.license = 'MIT'
10
+ gem.authors = ["Davis Gallinghouse"]
11
+ gem.email = ["davis@trailofbits.com"]
12
+ gem.description = %q{DMARC Record Parser}
13
+ gem.summary = %q{DMARC Record Parser}
14
+ gem.homepage = "https://github.com/trailofbits/dmarc#readme"
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'parslet', '~> 1.5'
21
+
22
+ gem.add_development_dependency 'bundler', '~> 1.0'
23
+ gem.add_development_dependency 'rake', '~> 10.0'
24
+ gem.add_development_dependency 'rspec', '~> 2.8'
25
+ gem.add_development_dependency 'yard', '~> 0.8'
26
+ end
@@ -0,0 +1,3 @@
1
+ require 'dmarc/record'
2
+ require 'dmarc/parser'
3
+ require 'dmarc/version'
@@ -0,0 +1,177 @@
1
+ require 'parslet'
2
+
3
+ module DMARC
4
+ class Parser < Parslet::Parser
5
+
6
+ root('dmarc_record')
7
+ rule('dmarc_record') do
8
+ dmarc_version >>
9
+ (dmarc_sep >> (
10
+ dmarc_request |
11
+ dmarc_srequest |
12
+ dmarc_auri |
13
+ dmarc_furi |
14
+ dmarc_adkim |
15
+ dmarc_aspf |
16
+ dmarc_ainterval |
17
+ dmarc_rfmt |
18
+ dmarc_percent
19
+ )).repeat >>
20
+ dmarc_sep.maybe
21
+ end
22
+
23
+ rule('dmarc_version') do
24
+ str('v') >> wsp.repeat >>
25
+ str('=') >> wsp.repeat >>
26
+ str('DMARC1').as(:v)
27
+ end
28
+ rule('dmarc_sep') { wsp.repeat >> str(';') >> wsp.repeat }
29
+
30
+ rule('dmarc_request') do
31
+ str('p') >> wsp.repeat >> str('=') >> wsp.repeat >> (
32
+ str('none') |
33
+ str('quarantine') |
34
+ str('reject')
35
+ ).as(:p)
36
+ end
37
+
38
+ rule('dmarc_srequest') do
39
+ str('sp') >> wsp.repeat >> str('=') >> wsp.repeat >> (
40
+ str('none') |
41
+ str('quarantine') |
42
+ str('reject')
43
+ ).as(:sp)
44
+ end
45
+
46
+ rule('dmarc_auri') do
47
+ str('rua') >> wsp.repeat >> str('=') >> wsp.repeat >>
48
+ dmarc_uri.as(:rua) >> (
49
+ wsp.repeat >> str(',') >> wsp.repeat >> dmarc_uri.as(:rua)
50
+ ).repeat
51
+ end
52
+
53
+ rule('dmarc_ainterval') do
54
+ str('ri') >> wsp.repeat >> str('=') >> wsp.repeat >> digit.repeat(1).as(:ri)
55
+ end
56
+
57
+ rule('dmarc_furi') do
58
+ str('ruf') >> wsp.repeat >> str('=') >> wsp.repeat >>
59
+ dmarc_uri.as(:ruf) >> (wsp.repeat >> str(',') >> wsp.repeat >> dmarc_uri.as(:ruf)).repeat
60
+ end
61
+
62
+ rule('dmarc_rfmt') do
63
+ str('rf') >> wsp.repeat >> str('=') >> wsp.repeat >> (
64
+ str('afrf') |
65
+ str('iodef')
66
+ ).as(:rf)
67
+ end
68
+
69
+ rule('dmarc_percent') do
70
+ str('pct') >> wsp.repeat >> str('=') >> wsp.repeat >> digit.repeat(1, 3).as(:pct)
71
+ end
72
+
73
+ rule('dmarc_adkim') do
74
+ str('adkim') >> wsp.repeat >> str('=') >> wsp.repeat >> (
75
+ str('r') |
76
+ str('s')
77
+ ).as(:adkim)
78
+ end
79
+
80
+ rule('dmarc_aspf') do
81
+ str('aspf') >> wsp.repeat >> str('=') >> wsp.repeat >> (
82
+ str('r') |
83
+ str('s')
84
+ ).as(:aspf)
85
+ end
86
+
87
+ rule('dmarc_uri') do
88
+ uri.as(:uri) >> (
89
+ str('!') >> digit.repeat(1).as(:size) >> (
90
+ str('k') |
91
+ str('m') |
92
+ str('g') |
93
+ str('t')
94
+ ).as(:unit).maybe
95
+ ).maybe
96
+ end
97
+
98
+ rule('uri') do
99
+ ( absoluteURI | relativeURI ).maybe >>
100
+ ( str('#') >> fragment ).maybe
101
+ end
102
+ rule('absoluteURI') { scheme >> str(':') >> ( hier_part | opaque_part ) }
103
+ rule('relativeURI') do
104
+ ( net_path | abs_path | rel_path ) >> ( str('?') >> query ).maybe
105
+ end
106
+
107
+ rule('hier_part') do
108
+ ( net_path | abs_path ) >> ( str('?') >> query )
109
+ end
110
+ rule('opaque_part') do
111
+ uric_no_slash >> uric.repeat
112
+ end
113
+
114
+ rule('uric_no_slash') do
115
+ unreserved | escaped | match('[?:@&=+$]')
116
+ end
117
+
118
+ rule('net_path') { str('//') >> authority >> abs_path.maybe }
119
+ rule('abs_path') { str('/') >> path_segments }
120
+ rule('rel_path') { rel_segment >> abs_path.maybe }
121
+
122
+ rule('rel_segment') { ( unreserved | escaped | match('[@&=+$]') ).repeat(1) }
123
+
124
+ rule('scheme') { alpha >> ( alpha | digit | match('[+-.]') ).repeat }
125
+
126
+ rule('authority') { server | reg_name }
127
+
128
+ rule('reg_name') { ( unreserved | escaped | match('[$:@&=+]') ).repeat(1) }
129
+
130
+ rule('server') { ( ( userinfo >> str('@') ).maybe >> hostport ).maybe }
131
+ rule('userinfo') { ( unreserved | escaped | match('[:&=+$]') ).repeat }
132
+
133
+ rule('hostport') { host >> ( str(':') >> port ).maybe }
134
+ rule('host') { hostname | ipv4address }
135
+ rule('hostname') do
136
+ ( domainlabel >> str('.') ).repeat >> toplabel >> str('.').maybe
137
+ end
138
+ rule('domainlabel') do
139
+ alphanum | (
140
+ alphanum >> ( alphanum | str('-') ).repeat >> alphanum
141
+ )
142
+ end
143
+ rule('toplabel') do
144
+ alpha | (
145
+ alpha >> ( alphanum | str('-') ).repeat >> alphanum
146
+ )
147
+ end
148
+ rule('ipv4address') do
149
+ digit.repeat(1) >> str('.') >>
150
+ digit.repeat(1) >> str('.') >>
151
+ digit.repeat(1) >> str('.') >>
152
+ digit.repeat(1)
153
+ end
154
+ rule('port') { digit.repeat }
155
+
156
+ rule('path') { ( abs_path | opaque_part ).maybe }
157
+ rule('path_segments') { segment >> ( str('/') >> segment ).repeat }
158
+ rule('segment') { pchar.repeat >> ( str(';') >> param ).repeat }
159
+ rule('param') { pchar }
160
+ rule('pchar') { unreserved | escaped | match('[:@&=+$]') }
161
+
162
+ rule('query') { uric.repeat }
163
+ rule('fragment') { uric.repeat }
164
+
165
+ rule('uric') { reserved | unreserved | escaped }
166
+ rule('reserved') { match('[/?:@&=+$]') }
167
+ rule('unreserved') { alphanum | mark }
168
+ rule('mark') { match("[-_.~*'()]") }
169
+ rule('escaped') { str('%') >> hex >> hex }
170
+ rule('hex') { digit | match('[a-fA-F]') }
171
+ rule('alphanum') { alpha | digit }
172
+ rule('alpha') { match('[a-zA-Z]') }
173
+ rule('digit') { match('[0-9]') }
174
+ rule('wsp') { str(' ') | str("\t") }
175
+
176
+ end
177
+ end
@@ -0,0 +1,33 @@
1
+ require 'dmarc/parser'
2
+
3
+ module DMARC
4
+ class Record < Struct.new(:adkim, :aspf, :fo, :p, :pct, :rf, :ri, :rua, :ruf, :sp, :v)
5
+
6
+ def self.from_txt(rec)
7
+ new(DMARC::Parser.new.parse(rec))
8
+ end
9
+
10
+ DEFAULTS = {
11
+ adkim: 'r',
12
+ aspf: 'r',
13
+ fo: '0',
14
+ pct: 100,
15
+ rf: 'afrf',
16
+ ri: 86400,
17
+ }
18
+
19
+ def initialize(tags=[])
20
+ tags.reduce(DEFAULTS, &:merge).each_pair do |k,v|
21
+ case k
22
+ when :pct, :ri
23
+ self[k] = v.to_i
24
+ else
25
+ self[k] = v
26
+ end
27
+ end
28
+
29
+ self.sp ||= p
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,4 @@
1
+ module DMARC
2
+ # dmarc version
3
+ VERSION = '0.1.0'
4
+ end
@@ -0,0 +1,231 @@
1
+ require 'dmarc/parser'
2
+ require 'pp'
3
+
4
+ describe DMARC::Parser do
5
+ describe '#dmarc_uri' do
6
+ subject { described_class.new.dmarc_uri }
7
+
8
+ it 'parses mailto URIs' do
9
+ uri = 'mailto:user@example.org'
10
+ expect(subject.parse(uri)).to eq(uri: uri)
11
+ end
12
+
13
+ it 'parses mailto URIs with size' do
14
+ uri = 'mailto:user@example.org'
15
+ expect(subject.parse(uri + '!20')).to eq(uri: uri, size: '20')
16
+ end
17
+
18
+ it 'parses mailto URIs with size and unit' do
19
+ uri = 'mailto:user@example.org'
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 'ignores spacing' do
47
+ record1 = 'v=DMARC1;p=none;sp=reject'
48
+ record2 = 'v = DMARC1 ; p = none ; sp = reject'
49
+ expect(subject.parse record1).to eq(subject.parse record2)
50
+ end
51
+ end
52
+
53
+ describe '#dmarc_version' do
54
+ subject { described_class.new.dmarc_version }
55
+
56
+ it 'parses DMARC versions' do
57
+ expect(subject.parse('v=DMARC1')).to eq(v: 'DMARC1')
58
+ end
59
+
60
+ it 'ignores spacing' do
61
+ expect(subject.parse 'v = DMARC1').to eq(subject.parse 'v=DMARC1')
62
+ end
63
+ end
64
+
65
+ describe '#dmarc_request' do
66
+ subject { described_class.new.dmarc_request }
67
+
68
+ it 'parses "none" requests' do
69
+ expect(subject.parse('p=none')).to eq(p: 'none')
70
+ end
71
+
72
+ it 'parses quarantine requests' do
73
+ expect(subject.parse('p=quarantine')).to eq(p: 'quarantine')
74
+ end
75
+
76
+ it 'parses reject requests' do
77
+ expect(subject.parse('p=reject')).to eq(p: 'reject')
78
+ end
79
+
80
+ it 'ingores spacing' do
81
+ expect(subject.parse 'p=none').to eq(subject.parse 'p = none')
82
+ end
83
+ end
84
+
85
+ describe '#dmarc_srequest' do
86
+ subject { described_class.new.dmarc_srequest }
87
+
88
+ it 'parses "none" requests' do
89
+ expect(subject.parse('sp=none')).to eq(sp: 'none')
90
+ end
91
+
92
+ it 'parses quarantine requests' do
93
+ expect(subject.parse('sp=quarantine')).to eq(sp: 'quarantine')
94
+ end
95
+
96
+ it 'parses reject requests' do
97
+ expect(subject.parse('sp=reject')).to eq(sp: 'reject')
98
+ end
99
+
100
+ it 'ignores spacing' do
101
+ expect(subject.parse 'sp=none').to eq(subject.parse 'sp = none')
102
+ end
103
+ end
104
+
105
+ describe '#dmarc_auri' do
106
+ subject { described_class.new.dmarc_auri }
107
+
108
+ it 'parses one URI' do
109
+ expect(subject.parse('rua=mailto:user@example.org')).to eq(
110
+ rua: {
111
+ uri: 'mailto:user@example.org'
112
+ }
113
+ )
114
+ end
115
+
116
+ it 'parses many URIs' do
117
+ expect(
118
+ 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
+ ])
123
+ end
124
+ end
125
+
126
+ describe '#dmarc_ainterval' do
127
+ subject { described_class.new.dmarc_ainterval }
128
+
129
+ it 'parses a one digit interval' do
130
+ expect(subject.parse('ri=1')).to eq(ri: '1')
131
+ end
132
+
133
+ it 'parses a many digit interval' do
134
+ expect(subject.parse('ri=86400')).to eq(ri: '86400')
135
+ end
136
+
137
+ it 'ignores spacing' do
138
+ expect(subject.parse 'ri = 86400').to eq(subject.parse 'ri=86400')
139
+ end
140
+ end
141
+
142
+ describe '#dmarc_furi' do
143
+ subject { described_class.new.dmarc_furi }
144
+
145
+ it 'parses one URI' do
146
+ expect(subject.parse('ruf=mailto:user@example.org')).to eq(
147
+ ruf: {
148
+ uri: 'mailto:user@example.org'
149
+ }
150
+ )
151
+ end
152
+
153
+ it 'parses many URIs' do
154
+ expect(
155
+ 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
+ ])
160
+ end
161
+ end
162
+
163
+ describe '#dmarc_rfmt' do
164
+ subject { described_class.new.dmarc_rfmt }
165
+
166
+ it 'parses afrf format' do
167
+ expect(subject.parse('rf=afrf')).to eq(rf: 'afrf')
168
+ end
169
+
170
+ it 'parses iodef format' do
171
+ expect(subject.parse('rf=iodef')).to eq(rf: 'iodef')
172
+ end
173
+
174
+ it 'ignores spacing' do
175
+ expect(subject.parse 'rf=iodef').to eq(subject.parse 'rf = iodef')
176
+ end
177
+ end
178
+
179
+ describe '#dmarc_percent' do
180
+ subject { described_class.new.dmarc_percent }
181
+
182
+ it 'parses a one-digit percent' do
183
+ expect(subject.parse('pct=1')).to eq(pct: '1')
184
+ end
185
+
186
+ it 'parses a two-digit percent' do
187
+ expect(subject.parse('pct=10')).to eq(pct: '10')
188
+ end
189
+
190
+ it 'parses a three-digit percent' do
191
+ expect(subject.parse('pct=100')).to eq(pct: '100')
192
+ end
193
+
194
+ it 'ignores spacing' do
195
+ expect(subject.parse 'pct = 100').to eq(subject.parse 'pct=100')
196
+ end
197
+ end
198
+
199
+ describe '#dmarc_adkim' do
200
+ subject { described_class.new.dmarc_adkim }
201
+
202
+ it 'parses a relaxed DKIM policy' do
203
+ expect(subject.parse('adkim=r')).to eq(adkim: 'r')
204
+ end
205
+
206
+ it 'parses a strict DKIM policy' do
207
+ expect(subject.parse('adkim=s')).to eq(adkim: 's')
208
+ end
209
+
210
+ it 'ignores spacing' do
211
+ expect(subject.parse 'adkim=r').to eq(subject.parse 'adkim = r')
212
+ end
213
+ end
214
+
215
+ describe '#dmarc_aspf' do
216
+ subject { described_class.new.dmarc_aspf }
217
+
218
+ it 'parses a relaxed SPF policy' do
219
+ expect(subject.parse('aspf=r')).to eq(aspf: 'r')
220
+ end
221
+
222
+ it 'parses a strict SPF policy' do
223
+ expect(subject.parse('aspf=s')).to eq(aspf: 's')
224
+ end
225
+
226
+ it 'ignores spacing' do
227
+ expect(subject.parse 'aspf=s').to eq(subject.parse 'aspf = s')
228
+ end
229
+ end
230
+ end
231
+
@@ -0,0 +1,52 @@
1
+ require 'dmarc/record'
2
+
3
+ describe DMARC::Record do
4
+ context 'by default' do
5
+ it 'has a relaxed DKIM alignment' do
6
+ expect(subject.adkim).to eq('r')
7
+ end
8
+
9
+ it 'has a relaxed SPF alignment' do
10
+ expect(subject.aspf).to eq('r')
11
+ end
12
+
13
+ it 'has failure reporting options of "0"' do
14
+ expect(subject.fo).to eq('0')
15
+ end
16
+
17
+ it 'has an application percentage of 100' do
18
+ expect(subject.pct).to eq(100)
19
+ end
20
+
21
+ it 'has an afrf report format' do
22
+ expect(subject.rf).to eq('afrf')
23
+ end
24
+
25
+ it 'has a report interval of 1 day' do
26
+ expect(subject.ri).to eq(86400)
27
+ end
28
+ end
29
+
30
+ describe '#initialize' do
31
+ let(:parse_tree) do
32
+ [
33
+ {v: 'DMARC1'},
34
+ {p: 'none'},
35
+ {adkim: 'r'},
36
+ ]
37
+ end
38
+
39
+ it 'assigns the fields to its properties' do
40
+ rec = described_class.new parse_tree
41
+ expect(rec.v).to eq('DMARC1')
42
+ expect(rec.p).to eq('none')
43
+ expect(rec.adkim).to eq('r')
44
+ end
45
+
46
+ it 'gives "sp" the same value as "p" if undefined' do
47
+ rec = described_class.new parse_tree
48
+ expect(rec.sp).to eq('none')
49
+ end
50
+ end
51
+ end
52
+
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dmarc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Davis Gallinghouse
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parslet
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
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
+ description: DMARC Record Parser
84
+ email:
85
+ - davis@trailofbits.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - .gitignore
91
+ - Gemfile
92
+ - LICENSE
93
+ - README.md
94
+ - Rakefile
95
+ - dmarc.gemspec
96
+ - lib/dmarc.rb
97
+ - lib/dmarc/parser.rb
98
+ - lib/dmarc/record.rb
99
+ - lib/dmarc/version.rb
100
+ - spec/lib/dmarc/parser_spec.rb
101
+ - spec/lib/dmarc/record_spec.rb
102
+ homepage: https://github.com/trailofbits/dmarc#readme
103
+ licenses:
104
+ - MIT
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.0.14
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: DMARC Record Parser
126
+ test_files:
127
+ - spec/lib/dmarc/parser_spec.rb
128
+ - spec/lib/dmarc/record_spec.rb
129
+ has_rdoc: