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