coppertone 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d50206e3df522c6100a46e2a6f64989d10da5774
4
- data.tar.gz: 773a9709e679f5d45e5a7a6f779112ffea0e9aff
3
+ metadata.gz: 98149a0b1ea7cda00c82becc04efa776fe05d04c
4
+ data.tar.gz: 4b5c342217d8ea99622fc3b7e0d75a2d49d008b5
5
5
  SHA512:
6
- metadata.gz: 2752ba8667d8f7d8458cfa0429a4140f59a42af7a7ad52881b5a75f1e4881d2b4038a5fb1a8ca22dff54ca7301941da42400b94f87ddf5b6972ffb9de3900c06
7
- data.tar.gz: 90cb6889a3ed66eb50663e0681ffc011641d20329c0ffb86b7d6af780d40189beb36fac80c78f4e1107295134d643a104bd7933c10ca99d265e693ff0590b90a
6
+ metadata.gz: 3e739ea5580ee84d251ffae5f170d1116e40a5c789b94320878727de04c4a3dc8e6ea00641e14a0454c8aba087b9579c133362b1075ca20d1a25cdbc3188e2ff
7
+ data.tar.gz: 89ce502d95dd1e60dafe3398d39788548f70758f3b435e3949f9fe7d62bc9356d12765b4898dd96d24eb6db76a6824dabbb74f0c5e05705c7ba015702092d9a3
data/.travis.yml CHANGED
@@ -5,5 +5,7 @@ rvm:
5
5
  - 2.0.0
6
6
  - 2.1.2
7
7
  - jruby-19mode
8
- - rbx
8
+ - rbx-2.2.10
9
+ allow_failures:
10
+ - rvm: rbx-2.2.10
9
11
 
@@ -1,11 +1,15 @@
1
1
  require 'coppertone/mechanism'
2
2
  require 'coppertone/qualifier'
3
+ require 'active_support/core_ext/module/delegation'
3
4
 
4
5
  module Coppertone
5
6
  # Instances of this class represent directive terms, as defined by the
6
7
  # SPF specification (see section 4.6.1).
7
8
  class Directive
8
9
  attr_reader :qualifier, :mechanism
10
+ delegate :context_dependent?, :dns_lookup_term?,
11
+ :includes_ptr?, to: :mechanism
12
+
9
13
  def initialize(qualifier, mechanism)
10
14
  @qualifier = qualifier
11
15
  @mechanism = mechanism
@@ -19,8 +23,9 @@ module Coppertone
19
23
  end
20
24
  end
21
25
 
22
- def context_dependent?
23
- mechanism.context_dependent?
26
+ def target_domain
27
+ fail NeedsContextError unless dns_lookup_term?
28
+ mechanism.target_domain
24
29
  end
25
30
 
26
31
  def all?
@@ -45,4 +45,8 @@ module Coppertone
45
45
  class TermLimitExceededError < PermerrorError; end
46
46
  class VoidLimitExceededError < PermerrorError; end
47
47
  class MXLimitExceededError < PermerrorError; end
48
+
49
+ # Raised when context is required to evaluate a value, but
50
+ # context is not available
51
+ class NeedsContextError < Coppertone::Error; end
48
52
  end
@@ -41,5 +41,10 @@ module Coppertone
41
41
  return false unless other.instance_of? self.class
42
42
  macro_text == other.macro_text
43
43
  end
44
+ alias_method :eql?, :==
45
+
46
+ def hash
47
+ macro_text.hash
48
+ end
44
49
  end
45
50
  end
@@ -27,6 +27,21 @@ module Coppertone
27
27
  return false unless domain_spec
28
28
  domain_spec.includes_ptr?
29
29
  end
30
+
31
+ def target_domain
32
+ fail Coppertone::NeedsContextError if context_dependent?
33
+ domain_spec.to_s
34
+ end
35
+
36
+ def ==(other)
37
+ return false unless other.instance_of? self.class
38
+ domain_spec == other.domain_spec
39
+ end
40
+ alias_method :eql?, :==
41
+
42
+ def hash
43
+ domain_spec.hash
44
+ end
30
45
  end
31
46
  end
32
47
  end
@@ -37,11 +37,6 @@ module Coppertone
37
37
  def handle_invalid_domain(_macro_context, _options)
38
38
  fail RecordParsingError
39
39
  end
40
-
41
- def ==(other)
42
- return false unless other.instance_of? self.class
43
- domain_spec == other.domain_spec
44
- end
45
40
  end
46
41
  end
47
42
  end
@@ -28,11 +28,6 @@ module Coppertone
28
28
  def handle_invalid_domain(_macro_context, _options)
29
29
  fail RecordParsingError
30
30
  end
31
-
32
- def ==(other)
33
- return false unless other.instance_of? self.class
34
- domain_spec == other.domain_spec
35
- end
36
31
  end
37
32
  end
38
33
  end
@@ -25,14 +25,6 @@ module Coppertone
25
25
  @ip_v6_cidr_length ||= 128
26
26
  end
27
27
 
28
- def cidr_length(macro_context)
29
- if macro_context.original_ip_v6?
30
- ip_v6_cidr_length
31
- else
32
- ip_v4_cidr_length
33
- end
34
- end
35
-
36
28
  def match?(macro_context, request_context)
37
29
  request_context.register_dns_lookup_term
38
30
  target_name = generate_target_name(macro_context, request_context)
@@ -47,6 +39,7 @@ module Coppertone
47
39
  def parse_argument(attributes)
48
40
  fail InvalidMechanismError if attributes.blank?
49
41
  cidr_matches = CIDR_REGEXP.match(attributes)
42
+ fail InvalidMechanismError unless cidr_matches
50
43
  macro_string, raw_ip_v4_cidr_length, raw_ip_v6_cidr_length =
51
44
  clean_matches(attributes, cidr_matches)
52
45
  process_matches(macro_string, raw_ip_v4_cidr_length,
@@ -65,14 +58,10 @@ module Coppertone
65
58
  end
66
59
 
67
60
  def clean_matches(attributes, cidr_matches)
68
- if cidr_matches
69
- raw_ip_v4_cidr_length = cidr_matches[2] unless cidr_matches[2].blank?
70
- raw_ip_v6_cidr_length = cidr_matches[4] unless cidr_matches[4].blank?
71
- term = cidr_matches[0]
72
- domain_spec_end = term.blank? ? -1 : (-1 - term.length)
73
- else
74
- domain_spec_end = -1
75
- end
61
+ raw_ip_v4_cidr_length = cidr_matches[2] unless cidr_matches[2].blank?
62
+ raw_ip_v6_cidr_length = cidr_matches[4] unless cidr_matches[4].blank?
63
+ term = cidr_matches[0]
64
+ domain_spec_end = term.blank? ? -1 : (-1 - term.length)
76
65
  macro_string = parse_domain_spec(attributes, domain_spec_end)
77
66
  [macro_string, raw_ip_v4_cidr_length, raw_ip_v6_cidr_length]
78
67
  end
@@ -110,6 +99,11 @@ module Coppertone
110
99
  ip_v4_cidr_length == other.ip_v4_cidr_length &&
111
100
  ip_v6_cidr_length == other.ip_v6_cidr_length
112
101
  end
102
+ alias_method :eql?, :==
103
+
104
+ def hash
105
+ domain_spec.hash ^ ip_v4_cidr_length.hash ^ ip_v6_cidr_length.hash
106
+ end
113
107
  end
114
108
  end
115
109
  end
@@ -21,15 +21,6 @@ module Coppertone
21
21
  RecordFinder.new(request_context.dns_client, target_name).record
22
22
  end
23
23
 
24
- def context_dependent_result?(request_context,
25
- macro_context =
26
- Coppertone::NullMacroContext.new)
27
- target_name =
28
- target_name_from_domain_spec(macro_context, request_context)
29
- included_record(request_context, target_name)
30
- .context_dependent_result?(request_context)
31
- end
32
-
33
24
  def self.label
34
25
  'include'
35
26
  end
@@ -2,7 +2,7 @@ module Coppertone
2
2
  class Mechanism # rubocop:disable Style/Documentation
3
3
  # Implements the ip4 mechanism.
4
4
  class IPMechanism < Mechanism
5
- attr_reader :ip_network
5
+ attr_reader :netblock
6
6
  def self.create(attributes)
7
7
  new(attributes)
8
8
  end
@@ -11,9 +11,9 @@ module Coppertone
11
11
  super(attributes)
12
12
  unless attributes.blank?
13
13
  attributes = attributes[1..-1] if attributes[0] == ':'
14
- @ip_network = parse_ip_network(attributes)
14
+ @netblock = parse_netblock(attributes)
15
15
  end
16
- fail Coppertone::InvalidMechanismError if @ip_network.nil?
16
+ fail Coppertone::InvalidMechanismError if @netblock.nil?
17
17
  end
18
18
 
19
19
  LEADING_ZEROES_IN_CIDR_REGEXP = /\/0\d/
@@ -29,7 +29,7 @@ module Coppertone
29
29
  IP_PARSE_ERROR = IPAddr::Error
30
30
  end
31
31
 
32
- def parse_ip_network(ip_as_s)
32
+ def parse_netblock(ip_as_s)
33
33
  validate_no_leading_zeroes_in_cidr(ip_as_s)
34
34
  addr, cidr_length, dual = ip_as_s.split('/')
35
35
  return nil if dual
@@ -43,13 +43,13 @@ module Coppertone
43
43
  def match?(macro_context, _request_context)
44
44
  ip = ip_for_match(macro_context)
45
45
  return false unless ip
46
- return false unless ip.ipv4? == @ip_network.ipv4?
47
- @ip_network.include?(ip)
46
+ return false unless ip.ipv4? == @netblock.ipv4?
47
+ @netblock.include?(ip)
48
48
  end
49
49
 
50
50
  def ==(other)
51
51
  return false unless other.instance_of? self.class
52
- ip_network == other.ip_network
52
+ netblock == other.netblock
53
53
  end
54
54
  end
55
55
  end
@@ -16,11 +16,9 @@ module Coppertone
16
16
  RedirectRecordFinder.new(self, macro_context, request_context).record
17
17
  end
18
18
 
19
- def context_dependent_result?(request_context,
20
- macro_context =
21
- Coppertone::NullMacroContext.new)
22
- included_record(macro_context, request_context)
23
- .context_dependent_result?(request_context)
19
+ def target_domain
20
+ fail NeedsContextError if context_dependent?
21
+ arguments
24
22
  end
25
23
 
26
24
  def self.label
@@ -1,12 +1,10 @@
1
1
  module Coppertone
2
2
  class Modifier # rubocop:disable Style/Documentation
3
3
  class Unknown < Modifier
4
- def self.create(attributes)
5
- new(attributes)
6
- end
7
-
8
- def initialize(attributes)
4
+ attr_reader :label
5
+ def initialize(label, attributes)
9
6
  super(attributes)
7
+ @label = label
10
8
  @macro_string = Coppertone::MacroString.new(attributes)
11
9
  rescue Coppertone::MacroStringParsingError
12
10
  raise Coppertone::InvalidModifierError
@@ -19,11 +17,6 @@ module Coppertone
19
17
  def includes_ptr?
20
18
  false
21
19
  end
22
-
23
- def self.label
24
- 'unknown'
25
- end
26
20
  end
27
- register(Coppertone::Modifier::Unknown)
28
21
  end
29
22
  end
@@ -20,7 +20,11 @@ module Coppertone
20
20
  return nil unless matches
21
21
  type = matches[1]
22
22
  attributes = matches[2]
23
- build(type, attributes) || build('unknown', attributes)
23
+ build(type, attributes) || build_unknown(type, attributes)
24
+ end
25
+
26
+ def self.build_unknown(type, attributes)
27
+ Coppertone::Modifier::Unknown.new(type, attributes)
24
28
  end
25
29
 
26
30
  attr_reader :arguments
@@ -28,8 +32,12 @@ module Coppertone
28
32
  @arguments = arguments
29
33
  end
30
34
 
35
+ def label
36
+ self.class.label
37
+ end
38
+
31
39
  def to_s
32
- "#{self.class.label}=#{arguments}"
40
+ "#{label}=#{arguments}"
33
41
  end
34
42
  end
35
43
  end
@@ -29,13 +29,12 @@ module Coppertone
29
29
  all_directive ? true : false
30
30
  end
31
31
 
32
- def default_result
33
- return Result.neutral unless all_directive
34
- Result.from_directive(all_directive)
35
- end
36
-
37
- def safe_to_include?
38
- include_all?
32
+ def dns_lookup_term_count
33
+ @dns_lookup_term_count ||=
34
+ begin
35
+ base = redirect.nil? ? 0 : 1
36
+ base + directives.select(&:dns_lookup_term?).size
37
+ end
39
38
  end
40
39
 
41
40
  def includes
@@ -51,32 +50,36 @@ module Coppertone
51
50
  @modifiers ||= @terms.select { |t| t.is_a?(Coppertone::Modifier) }
52
51
  end
53
52
 
54
- def find_redirect
55
- find_modifier(Coppertone::Modifier::Redirect)
56
- end
57
-
58
53
  def redirect
59
54
  @redirect ||= find_redirect
60
55
  end
61
56
 
62
- def exp
63
- @exp ||= find_modifier(Coppertone::Modifier::Exp)
57
+ def redirect_with_directives?
58
+ redirect && directives.any?
64
59
  end
65
60
 
66
- def context_dependent_result?(request_context)
61
+ def netblock_mechanisms
62
+ @netblock_mechanisms ||=
63
+ directives.select { |d| d.mechanism.is_a?(Coppertone::Mechanism::IPMechanism) }
64
+ end
65
+
66
+ def netblocks_only?
67
+ return false if redirect
68
+ directives.reject(&:all?).reject do |d|
69
+ d.mechanism.is_a?(Coppertone::Mechanism::IPMechanism)
70
+ end.empty?
71
+ end
72
+
73
+ def context_dependent_evaluation?
67
74
  return true if directives.exist?(&:context_dependent)
68
- return true if context_dependent_redirect_result?(request_context)
69
- return false if includes.blank?
70
- includes.exists? { |i| i.context_dependent_result?(request_context) }
75
+ redirect && redirect.context_dependent?
71
76
  end
72
77
 
73
- def context_dependent_redirect_result?(request_context)
74
- return false unless redirect
75
- redirect.context_dependent? ||
76
- redirect.context_dependent_result?(request_context)
78
+ def exp
79
+ @exp ||= find_modifier(Coppertone::Modifier::Exp)
77
80
  end
78
81
 
79
- def context_dependent_message?
82
+ def context_dependent_explanation?
80
83
  exp && exp.context_dependent?
81
84
  end
82
85
 
@@ -93,6 +96,10 @@ module Coppertone
93
96
  arr.first
94
97
  end
95
98
 
99
+ def find_redirect
100
+ find_modifier(Coppertone::Modifier::Redirect)
101
+ end
102
+
96
103
  def self.version_str
97
104
  Coppertone::RecordTermParser::VERSION_STR
98
105
  end
@@ -10,22 +10,16 @@ module Coppertone
10
10
  RECORD_REGEXP.match(text.strip) ? true : false
11
11
  end
12
12
 
13
- attr_reader :terms
13
+ attr_reader :text, :terms
14
14
  def initialize(text)
15
15
  fail RecordParsingError unless self.class.record?(text)
16
16
  fail RecordParsingError unless ALLOWED_CHARACTERS.match(text)
17
- @terms = term_tokens(text).map { |token| parse_token(token) }
17
+ @text = text
18
+ @terms = Coppertone::TermsParser.new(terms_segment).terms
18
19
  end
19
20
 
20
- def term_tokens(text)
21
- text_without_prefix = text[VERSION_STR.length..-1]
22
- text_without_prefix.strip.split(/ /).select { |s| !s.blank? }
23
- end
24
-
25
- def parse_token(token)
26
- term = Term.build_from_token(token)
27
- fail RecordParsingError unless term
28
- term
21
+ def terms_segment
22
+ text[VERSION_STR.length..-1].strip
29
23
  end
30
24
  end
31
25
  end
@@ -0,0 +1,23 @@
1
+ module Coppertone
2
+ # Parses a un-prefixed string into terms
3
+ class TermsParser
4
+ attr_reader :text
5
+ def initialize(text)
6
+ @text = text
7
+ end
8
+
9
+ def terms
10
+ tokens.map { |token| parse_token(token) }
11
+ end
12
+
13
+ def tokens
14
+ text.split(/ /).select { |s| !s.blank? }
15
+ end
16
+
17
+ def parse_token(token)
18
+ term = Term.build_from_token(token)
19
+ fail RecordParsingError unless term
20
+ term
21
+ end
22
+ end
23
+ end
@@ -26,6 +26,11 @@ module Coppertone
26
26
  NO_DASH_REGEXP.match(l) || DASH_REGEXP.match(l)
27
27
  end
28
28
 
29
+ def self.valid_ldh_domain?(domain)
30
+ return false unless valid?(domain)
31
+ to_labels(domain).all? { |l| valid_hostname_label?(l) }
32
+ end
33
+
29
34
  def self.valid_label?(l)
30
35
  (l.length >= 0) && (l.length <= 63)
31
36
  end
@@ -1,3 +1,3 @@
1
1
  module Coppertone # rubocop:disable Style/Documentation
2
- VERSION = '0.0.3'.freeze
2
+ VERSION = '0.0.4'.freeze
3
3
  end
data/lib/coppertone.rb CHANGED
@@ -42,6 +42,7 @@ require 'coppertone/domain_spec'
42
42
  require 'coppertone/directive'
43
43
  require 'coppertone/modifier'
44
44
  require 'coppertone/term'
45
+ require 'coppertone/terms_parser'
45
46
  require 'coppertone/record_term_parser'
46
47
  require 'coppertone/record'
47
48
  require 'coppertone/record_evaluator'
@@ -51,4 +51,37 @@ describe Coppertone::Directive do
51
51
  end
52
52
  end
53
53
  end
54
+
55
+ context '#target_domain' do
56
+ it 'yields the target domain when the mechanism is not context dependent' do
57
+ d = Coppertone::Term.build_from_token('include:_spf.example.org')
58
+ expect(d.target_domain).to eq('_spf.example.org')
59
+ end
60
+
61
+ it 'raises an error when the mechanism is context dependent' do
62
+ d = Coppertone::Term.build_from_token('include:_spf.%{h}.example.org')
63
+ expect do
64
+ d.target_domain
65
+ end.to raise_error Coppertone::NeedsContextError
66
+ end
67
+
68
+ it 'raises an error when the mechanism does not support a target domain' do
69
+ d = Coppertone::Term.build_from_token('ip4:1.2.3.4')
70
+ expect do
71
+ d.target_domain
72
+ end.to raise_error Coppertone::NeedsContextError
73
+ end
74
+ end
75
+
76
+ context '#to_s' do
77
+ it 'should hide a default qualifier' do
78
+ d = Coppertone::Term.build_from_token('~include:_spf.%{h}.example.org')
79
+ expect(d.to_s).to eq('~include:_spf.%{h}.example.org')
80
+ end
81
+
82
+ it 'should hide a default qualifier' do
83
+ d = Coppertone::Term.build_from_token('+include:_spf.%{h}.example.org')
84
+ expect(d.to_s).to eq('include:_spf.%{h}.example.org')
85
+ end
86
+ end
54
87
  end
@@ -11,6 +11,7 @@ describe Coppertone::Mechanism::A do
11
11
  expect(mech.to_s).to eq('a')
12
12
  expect(mech).not_to be_includes_ptr
13
13
  expect(mech).to be_context_dependent
14
+ expect(mech).to be_dns_lookup_term
14
15
  end
15
16
 
16
17
  it 'should not fail if called with a blank argument' do
@@ -22,6 +23,7 @@ describe Coppertone::Mechanism::A do
22
23
  expect(mech.to_s).to eq('a')
23
24
  expect(mech).not_to be_includes_ptr
24
25
  expect(mech).to be_context_dependent
26
+ expect(mech).to be_dns_lookup_term
25
27
  end
26
28
 
27
29
  it 'should fail if called with an invalid macrostring' do
@@ -40,6 +42,7 @@ describe Coppertone::Mechanism::A do
40
42
  expect(mech.to_s).to eq('a:_spf.example.com')
41
43
  expect(mech).not_to be_includes_ptr
42
44
  expect(mech).not_to be_context_dependent
45
+ expect(mech).to be_dns_lookup_term
43
46
  end
44
47
 
45
48
  it 'should parse a context dependent domain spec' do
@@ -52,6 +55,7 @@ describe Coppertone::Mechanism::A do
52
55
  expect(mech.to_s).to eq('a:_spf.%{d}.example.com')
53
56
  expect(mech).not_to be_includes_ptr
54
57
  expect(mech).to be_context_dependent
58
+ expect(mech).to be_dns_lookup_term
55
59
  end
56
60
 
57
61
  it 'should parse a domain spec with a ptr' do
@@ -64,6 +68,7 @@ describe Coppertone::Mechanism::A do
64
68
  expect(mech.to_s).to eq('a:_spf.%{p}.example.com')
65
69
  expect(mech).to be_includes_ptr
66
70
  expect(mech).to be_context_dependent
71
+ expect(mech).to be_dns_lookup_term
67
72
  end
68
73
 
69
74
  it 'should parse a valid IP v4 CIDR length with a domain spec' do
@@ -7,6 +7,7 @@ describe Coppertone::Mechanism::All do
7
7
  expect(subject.to_s).to eq('all')
8
8
  expect(subject).not_to be_includes_ptr
9
9
  expect(subject).not_to be_context_dependent
10
+ expect(subject).not_to be_dns_lookup_term
10
11
  end
11
12
 
12
13
  it 'should not allow creation of new instances' do
@@ -28,6 +28,7 @@ describe Coppertone::Mechanism::Exists do
28
28
  expect(mech.to_s).to eq('exists:_spf.example.com')
29
29
  expect(mech).not_to be_includes_ptr
30
30
  expect(mech).not_to be_context_dependent
31
+ expect(mech).to be_dns_lookup_term
31
32
  end
32
33
 
33
34
  it 'should parse a context dependent domain spec' do
@@ -38,6 +39,7 @@ describe Coppertone::Mechanism::Exists do
38
39
  expect(mech.to_s).to eq('exists:_spf.%{d}.example.com')
39
40
  expect(mech).not_to be_includes_ptr
40
41
  expect(mech).to be_context_dependent
42
+ expect(mech).to be_dns_lookup_term
41
43
  end
42
44
 
43
45
  it 'should parse a domain spec with a ptr' do
@@ -48,6 +50,7 @@ describe Coppertone::Mechanism::Exists do
48
50
  expect(mech.to_s).to eq('exists:_spf.%{p}.example.com')
49
51
  expect(mech).to be_includes_ptr
50
52
  expect(mech).to be_context_dependent
53
+ expect(mech).to be_dns_lookup_term
51
54
  end
52
55
  end
53
56
 
@@ -28,6 +28,8 @@ describe Coppertone::Mechanism::Include do
28
28
  expect(mech.to_s).to eq('include:_spf.example.com')
29
29
  expect(mech).not_to be_includes_ptr
30
30
  expect(mech).not_to be_context_dependent
31
+ expect(mech).to be_dns_lookup_term
32
+ expect(mech.target_domain).to eq('_spf.example.com')
31
33
  end
32
34
 
33
35
  it 'should parse a context dependent domain spec' do
@@ -38,6 +40,10 @@ describe Coppertone::Mechanism::Include do
38
40
  expect(mech.to_s).to eq('include:_spf.%{d}.example.com')
39
41
  expect(mech).not_to be_includes_ptr
40
42
  expect(mech).to be_context_dependent
43
+ expect(mech).to be_dns_lookup_term
44
+ expect do
45
+ mech.target_domain
46
+ end.to raise_error Coppertone::NeedsContextError
41
47
  end
42
48
 
43
49
  it 'should parse a domain spec with a ptr' do
@@ -48,6 +54,10 @@ describe Coppertone::Mechanism::Include do
48
54
  expect(mech.to_s).to eq('include:_spf.%{p}.example.com')
49
55
  expect(mech).to be_includes_ptr
50
56
  expect(mech).to be_context_dependent
57
+ expect(mech).to be_dns_lookup_term
58
+ expect do
59
+ mech.target_domain
60
+ end.to raise_error Coppertone::NeedsContextError
51
61
  end
52
62
  end
53
63
 
@@ -22,26 +22,29 @@ describe Coppertone::Mechanism::IP4 do
22
22
 
23
23
  it 'should not fail if called with an IP v6' do
24
24
  mech = Coppertone::Mechanism::IP4.new(':fe80::202:b3ff:fe1e:8329')
25
- expect(mech.ip_network).to eq(IPAddr.new('fe80::202:b3ff:fe1e:8329'))
25
+ expect(mech.netblock).to eq(IPAddr.new('fe80::202:b3ff:fe1e:8329'))
26
26
  expect(mech.to_s).to eq('ip4:fe80::202:b3ff:fe1e:8329')
27
27
  expect(mech).not_to be_includes_ptr
28
28
  expect(mech).not_to be_context_dependent
29
+ expect(mech).not_to be_dns_lookup_term
29
30
  end
30
31
 
31
32
  it 'should work if called with an IP4' do
32
33
  mech = Coppertone::Mechanism::IP4.new(':1.2.3.4')
33
- expect(mech.ip_network).to eq(IPAddr.new('1.2.3.4'))
34
+ expect(mech.netblock).to eq(IPAddr.new('1.2.3.4'))
34
35
  expect(mech.to_s).to eq('ip4:1.2.3.4')
35
36
  expect(mech).not_to be_includes_ptr
36
37
  expect(mech).not_to be_context_dependent
38
+ expect(mech).not_to be_dns_lookup_term
37
39
  end
38
40
 
39
41
  it 'should work if called with an IP4 with a pfxlen' do
40
42
  mech = Coppertone::Mechanism::IP4.new(':1.2.3.4/4')
41
- expect(mech.ip_network).to eq(IPAddr.new('1.2.3.4/4'))
43
+ expect(mech.netblock).to eq(IPAddr.new('1.2.3.4/4'))
42
44
  expect(mech.to_s).to eq('ip4:1.2.3.4/4')
43
45
  expect(mech).not_to be_includes_ptr
44
46
  expect(mech).not_to be_context_dependent
47
+ expect(mech).not_to be_dns_lookup_term
45
48
  end
46
49
  end
47
50
 
@@ -66,17 +69,17 @@ describe Coppertone::Mechanism::IP4 do
66
69
 
67
70
  it 'should not fail if called with an IP v6' do
68
71
  mech = Coppertone::Mechanism::IP4.create(':fe80::202:b3ff:fe1e:8329')
69
- expect(mech.ip_network).to eq(IPAddr.new('fe80::202:b3ff:fe1e:8329'))
72
+ expect(mech.netblock).to eq(IPAddr.new('fe80::202:b3ff:fe1e:8329'))
70
73
  end
71
74
 
72
75
  it 'should work if called with an IP4' do
73
76
  mech = Coppertone::Mechanism::IP4.create(':1.2.3.4')
74
- expect(mech.ip_network).to eq(IPAddr.new('1.2.3.4'))
77
+ expect(mech.netblock).to eq(IPAddr.new('1.2.3.4'))
75
78
  end
76
79
 
77
80
  it 'should work if called with an IP4 with a pfxlen' do
78
81
  mech = Coppertone::Mechanism::IP4.create(':1.2.3.4/4')
79
- expect(mech.ip_network).to eq(IPAddr.new('1.2.3.4/4'))
82
+ expect(mech.netblock).to eq(IPAddr.new('1.2.3.4/4'))
80
83
  end
81
84
 
82
85
  it 'should fail if called with an invalid pfxlen' do
@@ -22,19 +22,22 @@ describe Coppertone::Mechanism::IP6 do
22
22
 
23
23
  it 'should not fail if called with an IP v4' do
24
24
  mech = Coppertone::Mechanism::IP6.new(':1.2.3.4')
25
- expect(mech.ip_network).to eq(IPAddr.new('1.2.3.4'))
25
+ expect(mech.netblock).to eq(IPAddr.new('1.2.3.4'))
26
+ expect(mech).not_to be_dns_lookup_term
26
27
  end
27
28
 
28
29
  it 'should work if called with an IP6' do
29
30
  mech = Coppertone::Mechanism::IP6.new(':fe80::202:b3ff:fe1e:8329')
30
- expect(mech.ip_network)
31
+ expect(mech.netblock)
31
32
  .to eq(IPAddr.new('fe80::202:b3ff:fe1e:8329'))
33
+ expect(mech).not_to be_dns_lookup_term
32
34
  end
33
35
 
34
36
  it 'should work if called with an IP6 with a pfxlen' do
35
37
  mech = Coppertone::Mechanism::IP6.new(':fe80::202:b3ff:fe1e:8329/64')
36
- expect(mech.ip_network)
38
+ expect(mech.netblock)
37
39
  .to eq(IPAddr.new('fe80::202:b3ff:fe1e:8329/64'))
40
+ expect(mech).not_to be_dns_lookup_term
38
41
  end
39
42
 
40
43
  it 'should fail if called with an invalid pfxlen' do
@@ -66,14 +69,14 @@ describe Coppertone::Mechanism::IP6 do
66
69
 
67
70
  it 'should not fail if called with an IP v4' do
68
71
  mech = Coppertone::Mechanism::IP6.create(':1.2.3.4')
69
- expect(mech.ip_network).to eq(IPAddr.new('1.2.3.4'))
72
+ expect(mech.netblock).to eq(IPAddr.new('1.2.3.4'))
70
73
  expect(mech).not_to be_includes_ptr
71
74
  expect(mech).not_to be_context_dependent
72
75
  end
73
76
 
74
77
  it 'should work if called with an IP6' do
75
78
  mech = Coppertone::Mechanism::IP6.create(':fe80::202:b3ff:fe1e:8329')
76
- expect(mech.ip_network)
79
+ expect(mech.netblock)
77
80
  .to eq(IPAddr.new('fe80::202:b3ff:fe1e:8329'))
78
81
  expect(mech).not_to be_includes_ptr
79
82
  expect(mech).not_to be_context_dependent
@@ -81,7 +84,7 @@ describe Coppertone::Mechanism::IP6 do
81
84
 
82
85
  it 'should work if called with an IP6 with a pfxlen' do
83
86
  mech = Coppertone::Mechanism::IP6.create(':fe80::202:b3ff:fe1e:8329/64')
84
- expect(mech.ip_network)
87
+ expect(mech.netblock)
85
88
  .to eq(IPAddr.new('fe80::202:b3ff:fe1e:8329/64'))
86
89
  expect(mech).not_to be_includes_ptr
87
90
  expect(mech).not_to be_context_dependent
@@ -11,6 +11,7 @@ describe Coppertone::Mechanism::MX do
11
11
  expect(mech.to_s).to eq('mx')
12
12
  expect(mech).not_to be_includes_ptr
13
13
  expect(mech).to be_context_dependent
14
+ expect(mech).to be_dns_lookup_term
14
15
  end
15
16
 
16
17
  it 'should not fail if called with a blank argument' do
@@ -22,6 +23,7 @@ describe Coppertone::Mechanism::MX do
22
23
  expect(mech.to_s).to eq('mx')
23
24
  expect(mech).not_to be_includes_ptr
24
25
  expect(mech).to be_context_dependent
26
+ expect(mech).to be_dns_lookup_term
25
27
  end
26
28
 
27
29
  it 'should fail if called with an invalid macrostring' do
@@ -39,6 +41,7 @@ describe Coppertone::Mechanism::MX do
39
41
  expect(mech.to_s).to eq('mx/24')
40
42
  expect(mech).not_to be_includes_ptr
41
43
  expect(mech).to be_context_dependent
44
+ expect(mech).to be_dns_lookup_term
42
45
  end
43
46
 
44
47
  it 'should process the domain spec if it includes a IP v6 CIDR' do
@@ -61,6 +64,7 @@ describe Coppertone::Mechanism::MX do
61
64
  expect(mech.to_s).to eq('mx/28//96')
62
65
  expect(mech).not_to be_includes_ptr
63
66
  expect(mech).to be_context_dependent
67
+ expect(mech).to be_dns_lookup_term
64
68
  end
65
69
 
66
70
  it 'should not fail if called with a fixed domain spec without explicit CIDRs' do
@@ -83,6 +87,7 @@ describe Coppertone::Mechanism::MX do
83
87
  expect(mech.ip_v4_cidr_length).to eq(28)
84
88
  expect(mech.ip_v6_cidr_length).to eq(96)
85
89
  expect(mech.to_s).to eq('mx:mx.example.com/28//96')
90
+ expect(mech).to be_dns_lookup_term
86
91
  end
87
92
 
88
93
  it 'should not fail if called with a context-dependent domain spec without explicit CIDRs' do
@@ -95,6 +100,7 @@ describe Coppertone::Mechanism::MX do
95
100
  expect(mech.to_s).to eq('mx:%{d}.example.com')
96
101
  expect(mech).not_to be_includes_ptr
97
102
  expect(mech).to be_context_dependent
103
+ expect(mech).to be_dns_lookup_term
98
104
  end
99
105
 
100
106
  it 'should not fail if called with a fixed domain spec with explicit CIDRs' do
@@ -107,6 +113,7 @@ describe Coppertone::Mechanism::MX do
107
113
  expect(mech.to_s).to eq('mx:%{d}.example.com/28//96')
108
114
  expect(mech).not_to be_includes_ptr
109
115
  expect(mech).to be_context_dependent
116
+ expect(mech).to be_dns_lookup_term
110
117
  end
111
118
 
112
119
  it 'should not fail if called with a context-dependent domain spec without explicit CIDRs with PTR' do
@@ -119,6 +126,7 @@ describe Coppertone::Mechanism::MX do
119
126
  expect(mech.to_s).to eq('mx:%{p}.example.com')
120
127
  expect(mech).to be_includes_ptr
121
128
  expect(mech).to be_context_dependent
129
+ expect(mech).to be_dns_lookup_term
122
130
  end
123
131
 
124
132
  it 'should not fail if called with a fixed domain spec with explicit CIDRs with PTR' do
@@ -131,6 +139,7 @@ describe Coppertone::Mechanism::MX do
131
139
  expect(mech.to_s).to eq('mx:%{p}.example.com/28//96')
132
140
  expect(mech).to be_includes_ptr
133
141
  expect(mech).to be_context_dependent
142
+ expect(mech).to be_dns_lookup_term
134
143
  end
135
144
  end
136
145
 
@@ -8,6 +8,7 @@ describe Coppertone::Mechanism::Ptr do
8
8
  expect(mech.domain_spec).to be_nil
9
9
  expect(mech).not_to be_includes_ptr
10
10
  expect(mech).to be_context_dependent
11
+ expect(mech).to be_dns_lookup_term
11
12
  end
12
13
 
13
14
  it 'should not fail if called with a blank argument' do
@@ -16,6 +17,7 @@ describe Coppertone::Mechanism::Ptr do
16
17
  expect(mech.domain_spec).to be_nil
17
18
  expect(mech).not_to be_includes_ptr
18
19
  expect(mech).to be_context_dependent
20
+ expect(mech).to be_dns_lookup_term
19
21
  end
20
22
 
21
23
  it 'should fail if called with an invalid macrostring' do
@@ -11,6 +11,7 @@ describe Coppertone::Modifier::Redirect do
11
11
  .to eq('redirect=test.example.com')
12
12
  expect(modifier).not_to be_includes_ptr
13
13
  expect(modifier).not_to be_context_dependent
14
+ expect(modifier.target_domain).to eq('test.example.com')
14
15
  end
15
16
 
16
17
  it 'should work with a context independent domain spec' do
@@ -22,6 +23,9 @@ describe Coppertone::Modifier::Redirect do
22
23
  .to eq('redirect=%{d}.example.com')
23
24
  expect(modifier).not_to be_includes_ptr
24
25
  expect(modifier).to be_context_dependent
26
+ expect do
27
+ modifier.target_domain
28
+ end.to raise_error Coppertone::NeedsContextError
25
29
  end
26
30
 
27
31
  it 'should work with a context independent domain spec with a PTR' do
@@ -33,6 +37,9 @@ describe Coppertone::Modifier::Redirect do
33
37
  .to eq('redirect=%{p}.example.com')
34
38
  expect(modifier).to be_includes_ptr
35
39
  expect(modifier).to be_context_dependent
40
+ expect do
41
+ modifier.target_domain
42
+ end.to raise_error Coppertone::NeedsContextError
36
43
  end
37
44
  end
38
45
 
@@ -42,4 +49,19 @@ describe Coppertone::Modifier::Redirect do
42
49
  .to eq('redirect=test.example.com')
43
50
  end
44
51
  end
52
+
53
+ context '#included_record' do
54
+ it 'should delegate to included_record' do
55
+ modifier = Coppertone::Modifier::Redirect.new('test.example.com')
56
+ record = double(:record)
57
+ finder = double(:finder)
58
+ expect(finder).to receive(:record).and_return(record)
59
+ macro_ctx = double(:macro_ctx)
60
+ request_ctx = double(:request_ctx)
61
+ expect(Coppertone::RedirectRecordFinder).to receive(:new)
62
+ .with(modifier, macro_ctx, request_ctx).and_return(finder)
63
+ expect(modifier.included_record(macro_ctx, request_ctx))
64
+ .to eq(record)
65
+ end
66
+ end
45
67
  end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe Coppertone::Modifier::Unknown do
4
+ it 'should always be context independent and never require macro evaluation' do
5
+ unk = Coppertone::Modifier::Unknown.new('abcd', 'wxyz')
6
+ expect(unk).not_to be_context_dependent
7
+ expect(unk).not_to be_includes_ptr
8
+ expect(unk.to_s).to eq('abcd=wxyz')
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Coppertone::NullMacroContext do
4
+ %w(s l o d i p v h c r t).each do |i|
5
+ it "should raise an error for #{i}" do
6
+ expect do
7
+ Coppertone::NullMacroContext::NULL_CONTEXT.send(i)
8
+ end.to raise_error ArgumentError
9
+ end
10
+
11
+ it "should raise an error for #{i.upcase}" do
12
+ expect do
13
+ Coppertone::NullMacroContext::NULL_CONTEXT.send(i.upcase)
14
+ end.to raise_error ArgumentError
15
+ end
16
+ end
17
+
18
+ it 'should return self for with_domain' do
19
+ n = Coppertone::NullMacroContext::NULL_CONTEXT
20
+ expect(n.with_domain('abcd')).to eq(n)
21
+ end
22
+ end
@@ -11,6 +11,7 @@ describe Coppertone::Qualifier do
11
11
  it "should map from #{k} to the correct value" do
12
12
  expect(Coppertone::Qualifier.find_by_text(k)).to eq(v)
13
13
  expect(v.text).to eq(k)
14
+ expect(v.to_s).to eq(k)
14
15
  end
15
16
  end
16
17
  end
@@ -22,6 +23,15 @@ describe Coppertone::Qualifier do
22
23
  end
23
24
  end
24
25
 
26
+ context '#default?' do
27
+ it 'should produce the right values for default?' do
28
+ expect(Coppertone::Qualifier::PASS).to be_default
29
+ expect(Coppertone::Qualifier::FAIL).not_to be_default
30
+ expect(Coppertone::Qualifier::SOFTFAIL).not_to be_default
31
+ expect(Coppertone::Qualifier::NEUTRAL).not_to be_default
32
+ end
33
+ end
34
+
25
35
  context '#qualifiers' do
26
36
  it 'should have the correct contents' do
27
37
  qualifiers = Coppertone::Qualifier.qualifiers
data/spec/record_spec.rb CHANGED
@@ -40,6 +40,7 @@ describe Coppertone::Record do
40
40
  expect(directive.qualifier).to eq(Coppertone::Qualifier::SOFTFAIL)
41
41
  expect(directive.mechanism).to eq(Coppertone::Mechanism::All.instance)
42
42
  expect(record.modifiers).to be_empty
43
+ expect(record.to_s).to eq('v=spf1 ~all')
43
44
  end
44
45
 
45
46
  it 'be case insensitive when parsing the version string' do
@@ -50,6 +51,7 @@ describe Coppertone::Record do
50
51
  expect(directive.qualifier).to eq(Coppertone::Qualifier::SOFTFAIL)
51
52
  expect(directive.mechanism).to eq(Coppertone::Mechanism::All.instance)
52
53
  expect(record.modifiers).to be_empty
54
+ expect(record.to_s).to eq('v=spf1 ~all')
53
55
  end
54
56
 
55
57
  it 'should parse more complex records' do
@@ -71,6 +73,7 @@ describe Coppertone::Record do
71
73
  expect(record.redirect).to be_nil
72
74
  expect(record.exp)
73
75
  .to eq(Coppertone::Modifier::Exp.new('explain._spf.%{d}'))
76
+ expect(record.to_s).to eq('v=spf1 mx -all exp=explain._spf.%{d}')
74
77
  end
75
78
 
76
79
  it 'should fail on more records with duplicate modifiers' do
@@ -118,4 +121,27 @@ describe Coppertone::Record do
118
121
  expect(Coppertone::Record.new('v=spf1 mx -all redirect=explain._spf.%{d}').unknown_modifiers).to be_empty
119
122
  end
120
123
  end
124
+
125
+ context '#dns_lookup_term_count' do
126
+ it 'should calculate correctly' do
127
+ expect(Coppertone::Record.new('v=spf1 -all exp=explain._spf.%{d}').dns_lookup_term_count).to eq(0)
128
+ expect(Coppertone::Record.new('v=spf1 a:example.test.com -exists:some.domain.com ~all').dns_lookup_term_count).to eq(2)
129
+ expect(Coppertone::Record.new('v=spf1 ip4:1.2.3.4 -exists:some.domain.com ~all').dns_lookup_term_count).to eq(1)
130
+ end
131
+ end
132
+
133
+ context '#includes' do
134
+ it 'should yield an empty array when there are no include' do
135
+ expect(Coppertone::Record.new('v=spf1 mx -all exp=explain._spf.%{d}').includes).to be_empty
136
+ expect(Coppertone::Record.new('v=spf1 ip4:1.2.3.4 ~all').includes).to be_empty
137
+ end
138
+
139
+ it 'should yield a set of the includes, in order' do
140
+ r = Coppertone::Record
141
+ .new('v=spf1 include:_spf.domain1.com ip4:1.2.3.4 include:_spf.domain2.com ~all exp=explain._spf.%{d}')
142
+ includes = r.includes
143
+ expect(includes.map(&:mechanism).all? {|i| i.is_a?(Coppertone::Mechanism::Include)}).to be_truthy
144
+ expect(includes.map(&:target_domain)).to eq(%w(_spf.domain1.com _spf.domain2.com))
145
+ end
146
+ end
121
147
  end
metadata CHANGED
@@ -1,136 +1,138 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coppertone
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter M. Goldstein
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-10 00:00:00.000000000 Z
11
+ date: 2014-11-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
+ name: dns_adapter
14
15
  requirement: !ruby/object:Gem::Requirement
15
16
  requirements:
16
- - - '>='
17
+ - - ">="
17
18
  - !ruby/object:Gem::Version
18
19
  version: '0'
19
- name: dns_adapter
20
- prerelease: false
21
20
  type: :runtime
21
+ prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
+ name: i18n
28
29
  requirement: !ruby/object:Gem::Requirement
29
30
  requirements:
30
- - - '>='
31
+ - - ">="
31
32
  - !ruby/object:Gem::Version
32
33
  version: '0'
33
- name: i18n
34
- prerelease: false
35
34
  type: :runtime
35
+ prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
+ name: addressable
42
43
  requirement: !ruby/object:Gem::Requirement
43
44
  requirements:
44
- - - '>='
45
+ - - ">="
45
46
  - !ruby/object:Gem::Version
46
47
  version: '0'
47
- name: addressable
48
- prerelease: false
49
48
  type: :runtime
49
+ prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
+ name: activesupport
56
57
  requirement: !ruby/object:Gem::Requirement
57
58
  requirements:
58
- - - '>='
59
+ - - ">="
59
60
  - !ruby/object:Gem::Version
60
61
  version: '3.0'
61
- name: activesupport
62
- prerelease: false
63
62
  type: :runtime
63
+ prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '3.0'
69
69
  - !ruby/object:Gem::Dependency
70
+ name: bundler
70
71
  requirement: !ruby/object:Gem::Requirement
71
72
  requirements:
72
- - - '>='
73
+ - - ">="
73
74
  - !ruby/object:Gem::Version
74
75
  version: '0'
75
- name: bundler
76
- prerelease: false
77
76
  type: :development
77
+ prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - '>='
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
+ name: rake
84
85
  requirement: !ruby/object:Gem::Requirement
85
86
  requirements:
86
- - - ~>
87
+ - - "~>"
87
88
  - !ruby/object:Gem::Version
88
89
  version: '10.0'
89
- name: rake
90
- prerelease: false
91
90
  type: :development
91
+ prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ~>
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '10.0'
97
97
  - !ruby/object:Gem::Dependency
98
+ name: rspec
98
99
  requirement: !ruby/object:Gem::Requirement
99
100
  requirements:
100
- - - '>='
101
+ - - ">="
101
102
  - !ruby/object:Gem::Version
102
103
  version: '3.0'
103
- name: rspec
104
- prerelease: false
105
104
  type: :development
105
+ prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - '>='
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '3.0'
111
111
  - !ruby/object:Gem::Dependency
112
+ name: rubocop
112
113
  requirement: !ruby/object:Gem::Requirement
113
114
  requirements:
114
- - - '>='
115
+ - - ">="
115
116
  - !ruby/object:Gem::Version
116
117
  version: '0'
117
- name: rubocop
118
- prerelease: false
119
118
  type: :development
119
+ prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - '>='
122
+ - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
- description: Coppertone includes tools for parsing SPF DNS records, evaluating the result of SPF checks for received emails, and creating appropriate email headers from SPF results.
125
+ description: Coppertone includes tools for parsing SPF DNS records, evaluating the
126
+ result of SPF checks for received emails, and creating appropriate email headers
127
+ from SPF results.
126
128
  email:
127
129
  - peter.m.goldstein@gmail.com
128
130
  executables: []
129
131
  extensions: []
130
132
  extra_rdoc_files: []
131
133
  files:
132
- - .gitignore
133
- - .travis.yml
134
+ - ".gitignore"
135
+ - ".travis.yml"
134
136
  - Gemfile
135
137
  - LICENSE
136
138
  - README.md
@@ -183,6 +185,7 @@ files:
183
185
  - lib/coppertone/sender_identity.rb
184
186
  - lib/coppertone/spf_service.rb
185
187
  - lib/coppertone/term.rb
188
+ - lib/coppertone/terms_parser.rb
186
189
  - lib/coppertone/utils.rb
187
190
  - lib/coppertone/utils/domain_utils.rb
188
191
  - lib/coppertone/utils/host_utils.rb
@@ -208,7 +211,9 @@ files:
208
211
  - spec/mechanism_spec.rb
209
212
  - spec/modifier/exp_spec.rb
210
213
  - spec/modifier/redirect_spec.rb
214
+ - spec/modifier/unknown_spec.rb
211
215
  - spec/modifier_spec.rb
216
+ - spec/null_macro_context_spec.rb
212
217
  - spec/open_spf/ALL_mechanism_syntax_spec.rb
213
218
  - spec/open_spf/A_mechanism_syntax_spec.rb
214
219
  - spec/open_spf/EXISTS_mechanism_syntax_spec.rb
@@ -245,24 +250,24 @@ homepage: https://github.com/petergoldstein/coppertone
245
250
  licenses:
246
251
  - Apache
247
252
  metadata: {}
248
- post_install_message:
253
+ post_install_message:
249
254
  rdoc_options: []
250
255
  require_paths:
251
256
  - lib
252
257
  required_ruby_version: !ruby/object:Gem::Requirement
253
258
  requirements:
254
- - - '>='
259
+ - - ">="
255
260
  - !ruby/object:Gem::Version
256
261
  version: '0'
257
262
  required_rubygems_version: !ruby/object:Gem::Requirement
258
263
  requirements:
259
- - - '>='
264
+ - - ">="
260
265
  - !ruby/object:Gem::Version
261
266
  version: '0'
262
267
  requirements: []
263
- rubyforge_project:
264
- rubygems_version: 2.1.9
265
- signing_key:
268
+ rubyforge_project:
269
+ rubygems_version: 2.4.4
270
+ signing_key:
266
271
  specification_version: 4
267
272
  summary: A Sender Policy Framework (SPF) toolkit
268
273
  test_files:
@@ -285,7 +290,9 @@ test_files:
285
290
  - spec/mechanism_spec.rb
286
291
  - spec/modifier/exp_spec.rb
287
292
  - spec/modifier/redirect_spec.rb
293
+ - spec/modifier/unknown_spec.rb
288
294
  - spec/modifier_spec.rb
295
+ - spec/null_macro_context_spec.rb
289
296
  - spec/open_spf/ALL_mechanism_syntax_spec.rb
290
297
  - spec/open_spf/A_mechanism_syntax_spec.rb
291
298
  - spec/open_spf/EXISTS_mechanism_syntax_spec.rb