coppertone 0.0.7 → 0.0.12

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.
Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +79 -0
  3. data/.codeclimate.yml +28 -0
  4. data/.github/dependabot.yml +7 -0
  5. data/.rubocop.yml +47 -6
  6. data/.travis.yml +2 -6
  7. data/Gemfile +12 -2
  8. data/LICENSE +21 -201
  9. data/Rakefile +12 -6
  10. data/coppertone.gemspec +9 -9
  11. data/lib/coppertone/class_builder.rb +2 -0
  12. data/lib/coppertone/directive.rb +6 -1
  13. data/lib/coppertone/domain_spec.rb +5 -1
  14. data/lib/coppertone/error.rb +5 -0
  15. data/lib/coppertone/ip_address_wrapper.rb +5 -1
  16. data/lib/coppertone/macro_context.rb +11 -4
  17. data/lib/coppertone/macro_string.rb +4 -2
  18. data/lib/coppertone/macro_string/macro_expand.rb +6 -2
  19. data/lib/coppertone/macro_string/macro_literal.rb +1 -0
  20. data/lib/coppertone/macro_string/macro_parser.rb +8 -4
  21. data/lib/coppertone/macro_string/macro_static_expand.rb +3 -1
  22. data/lib/coppertone/mechanism.rb +2 -0
  23. data/lib/coppertone/mechanism/all.rb +1 -0
  24. data/lib/coppertone/mechanism/cidr_parser.rb +3 -3
  25. data/lib/coppertone/mechanism/domain_spec_mechanism.rb +10 -3
  26. data/lib/coppertone/mechanism/domain_spec_optional.rb +1 -0
  27. data/lib/coppertone/mechanism/domain_spec_required.rb +1 -0
  28. data/lib/coppertone/mechanism/domain_spec_with_dual_cidr.rb +8 -1
  29. data/lib/coppertone/mechanism/include_matcher.rb +3 -0
  30. data/lib/coppertone/mechanism/ip_mechanism.rb +20 -10
  31. data/lib/coppertone/mechanism/mx.rb +1 -0
  32. data/lib/coppertone/modifier.rb +3 -1
  33. data/lib/coppertone/modifier/base.rb +7 -2
  34. data/lib/coppertone/modifier/exp.rb +6 -2
  35. data/lib/coppertone/modifier/redirect.rb +1 -0
  36. data/lib/coppertone/modifier/unknown.rb +1 -0
  37. data/lib/coppertone/null_macro_context.rb +1 -1
  38. data/lib/coppertone/qualifier.rb +1 -0
  39. data/lib/coppertone/record.rb +6 -2
  40. data/lib/coppertone/record_evaluator.rb +5 -0
  41. data/lib/coppertone/record_finder.rb +2 -2
  42. data/lib/coppertone/record_term_parser.rb +9 -5
  43. data/lib/coppertone/redirect_record_finder.rb +3 -1
  44. data/lib/coppertone/request.rb +26 -23
  45. data/lib/coppertone/request_context.rb +1 -0
  46. data/lib/coppertone/request_count_limiter.rb +2 -0
  47. data/lib/coppertone/result.rb +2 -1
  48. data/lib/coppertone/sender_identity.rb +2 -1
  49. data/lib/coppertone/term.rb +1 -0
  50. data/lib/coppertone/terms_parser.rb +3 -1
  51. data/lib/coppertone/utils/domain_utils.rb +21 -8
  52. data/lib/coppertone/utils/ip_in_domain_checker.rb +1 -1
  53. data/lib/coppertone/utils/validated_domain_finder.rb +2 -1
  54. data/lib/coppertone/version.rb +1 -1
  55. data/spec/domain_spec_spec.rb +1 -1
  56. data/spec/macro_string/macro_static_expand_spec.rb +2 -2
  57. data/spec/mechanism/ip4_spec.rb +3 -0
  58. data/spec/mechanism/ip6_spec.rb +3 -0
  59. data/spec/null_macro_context_spec.rb +1 -1
  60. data/spec/open_spf/Initial_processing_spec.rb +0 -2
  61. data/spec/open_spf/Semantics_of_exp_and_other_modifiers_spec.rb +0 -2
  62. data/spec/record_spec.rb +1 -1
  63. data/spec/spec_helper.rb +14 -3
  64. data/spec/term_spec.rb +1 -1
  65. data/spec/utils/domain_utils_spec.rb +10 -2
  66. metadata +34 -32
@@ -4,10 +4,10 @@ module Coppertone
4
4
  class CidrParser
5
5
  def self.parse(raw_length, max_val)
6
6
  return if raw_length.blank?
7
+
7
8
  length_as_i = raw_length.to_i
8
- if length_as_i < 0 || length_as_i > max_val
9
- raise Coppertone::InvalidMechanismError
10
- end
9
+ raise Coppertone::InvalidMechanismError if length_as_i.negative? || length_as_i > max_val
10
+
11
11
  length_as_i
12
12
  end
13
13
  end
@@ -5,14 +5,17 @@ module Coppertone
5
5
  attr_reader :domain_spec
6
6
 
7
7
  def target_name_from_domain_spec(macro_context, request_context)
8
- domain =
9
- domain_spec.expand(macro_context, request_context) if domain_spec
8
+ if domain_spec
9
+ domain =
10
+ domain_spec.expand(macro_context, request_context)
11
+ end
10
12
  Coppertone::Utils::DomainUtils.macro_expanded_domain(domain)
11
13
  end
12
14
 
13
15
  def trim_domain_spec(raw_domain_spec)
14
16
  return nil if raw_domain_spec.blank?
15
- raw_domain_spec[1..-1]
17
+
18
+ raw_domain_spec[1..]
16
19
  end
17
20
 
18
21
  def self.dns_lookup_term?
@@ -21,21 +24,25 @@ module Coppertone
21
24
 
22
25
  def context_dependent?
23
26
  return true unless domain_spec
27
+
24
28
  domain_spec.context_dependent?
25
29
  end
26
30
 
27
31
  def includes_ptr?
28
32
  return false unless domain_spec
33
+
29
34
  domain_spec.includes_ptr?
30
35
  end
31
36
 
32
37
  def target_domain
33
38
  raise Coppertone::NeedsContextError if context_dependent?
39
+
34
40
  domain_spec.to_s
35
41
  end
36
42
 
37
43
  def ==(other)
38
44
  return false unless other.instance_of? self.class
45
+
39
46
  domain_spec == other.domain_spec
40
47
  end
41
48
  alias eql? ==
@@ -11,6 +11,7 @@ module Coppertone
11
11
  def initialize(attributes)
12
12
  super(attributes)
13
13
  return if attributes.blank?
14
+
14
15
  raw_domain_spec = trim_domain_spec(attributes)
15
16
  @domain_spec = Coppertone::DomainSpec.new(raw_domain_spec)
16
17
  rescue Coppertone::MacroStringParsingError
@@ -10,6 +10,7 @@ module Coppertone
10
10
  super(attributes)
11
11
  raw_domain_spec = trim_domain_spec(attributes)
12
12
  raise InvalidMechanismError if raw_domain_spec.blank?
13
+
13
14
  @domain_spec = Coppertone::DomainSpec.new(raw_domain_spec)
14
15
  rescue Coppertone::MacroStringParsingError
15
16
  raise Coppertone::InvalidMechanismError
@@ -14,6 +14,7 @@ module Coppertone
14
14
  def initialize(attributes)
15
15
  super(attributes)
16
16
  return if attributes.blank?
17
+
17
18
  parse_argument(attributes)
18
19
  rescue Coppertone::MacroStringParsingError
19
20
  raise Coppertone::InvalidMechanismError
@@ -37,11 +38,13 @@ module Coppertone
37
38
  end
38
39
  end
39
40
 
40
- CIDR_REGEXP = %r{(/(\d*))?(//(\d*))?\z}
41
+ CIDR_REGEXP = %r{(/(\d*))?(//(\d*))?\z}.freeze
41
42
  def parse_argument(attributes)
42
43
  raise InvalidMechanismError if attributes.blank?
44
+
43
45
  cidr_matches = CIDR_REGEXP.match(attributes)
44
46
  raise InvalidMechanismError unless cidr_matches
47
+
45
48
  macro_string, raw_ip_v4_cidr_length, raw_ip_v6_cidr_length =
46
49
  clean_matches(attributes, cidr_matches)
47
50
  process_matches(macro_string, raw_ip_v4_cidr_length,
@@ -50,12 +53,15 @@ module Coppertone
50
53
 
51
54
  def parse_domain_spec(attributes, domain_spec_end)
52
55
  return nil if attributes.blank?
56
+
53
57
  cand = attributes[0..domain_spec_end]
54
58
  return nil if cand.blank?
59
+
55
60
  cand = trim_domain_spec(cand)
56
61
  # At this point we've ascertained that there is
57
62
  # a body to the domain spec
58
63
  raise InvalidMechanismError if cand.blank?
64
+
59
65
  cand
60
66
  end
61
67
 
@@ -97,6 +103,7 @@ module Coppertone
97
103
 
98
104
  def ==(other)
99
105
  return false unless other.instance_of? self.class
106
+
100
107
  domain_spec == other.domain_spec &&
101
108
  ip_v4_cidr_length == other.ip_v4_cidr_length &&
102
109
  ip_v6_cidr_length == other.ip_v6_cidr_length
@@ -13,17 +13,20 @@ module Coppertone
13
13
  def evaluate_none_result(result, m, r)
14
14
  new_result = super
15
15
  return new_result unless new_result.none?
16
+
16
17
  raise Coppertone::NoneIncludeResultError
17
18
  end
18
19
  end
19
20
 
20
21
  attr_reader :record
22
+
21
23
  def initialize(record)
22
24
  @record = record
23
25
  end
24
26
 
25
27
  def match?(macro_context, request_context)
26
28
  raise Coppertone::NoneIncludeResultError if record.nil?
29
+
27
30
  record_result =
28
31
  IncludeRecordEvaluator.new(record)
29
32
  .evaluate(macro_context, request_context)
@@ -2,7 +2,8 @@ module Coppertone
2
2
  class Mechanism
3
3
  # Implements the ip4 mechanism.
4
4
  class IPMechanism < Mechanism
5
- attr_reader :netblock
5
+ attr_reader :netblock, :cidr_length
6
+
6
7
  def self.create(attributes)
7
8
  new(attributes)
8
9
  end
@@ -10,15 +11,16 @@ module Coppertone
10
11
  def initialize(attributes)
11
12
  super(attributes)
12
13
  unless attributes.blank?
13
- attributes = attributes[1..-1] if attributes[0] == ':'
14
- @netblock = parse_netblock(attributes)
14
+ attributes = attributes[1..] if attributes[0] == ':'
15
+ @netblock, @cidr_length = parse_netblock(attributes)
15
16
  end
16
17
  raise Coppertone::InvalidMechanismError if @netblock.nil?
17
18
  end
18
19
 
19
- LEADING_ZEROES_IN_CIDR_REGEXP = %r{\/0\d}
20
+ LEADING_ZEROES_IN_CIDR_REGEXP = %r{/0\d}.freeze
20
21
  def validate_no_leading_zeroes_in_cidr(ip_as_s)
21
- return unless LEADING_ZEROES_IN_CIDR_REGEXP.match(ip_as_s)
22
+ return unless LEADING_ZEROES_IN_CIDR_REGEXP.match?(ip_as_s)
23
+
22
24
  raise Coppertone::InvalidMechanismError
23
25
  end
24
26
 
@@ -31,24 +33,32 @@ module Coppertone
31
33
 
32
34
  def parse_netblock(ip_as_s)
33
35
  validate_no_leading_zeroes_in_cidr(ip_as_s)
34
- addr, cidr_length, dual = ip_as_s.split('/')
35
- return nil if dual
36
+ addr, cidr_length_as_s, dual = ip_as_s.split('/')
37
+ return [nil, nil] if dual
38
+
36
39
  network = IPAddr.new(addr)
37
- network = network.mask(cidr_length.to_i) unless cidr_length.blank?
38
- network
40
+ network = network.mask(cidr_length_as_s.to_i) unless cidr_length_as_s.blank?
41
+ cidr_length = cidr_length_as_s.blank? ? default_cidr(network) : cidr_length_as_s.to_i
42
+ [network, cidr_length]
39
43
  rescue IP_PARSE_ERROR
40
- nil
44
+ [nil, nil]
45
+ end
46
+
47
+ def default_cidr(network)
48
+ network.ipv6? ? 128 : 32
41
49
  end
42
50
 
43
51
  def match?(macro_context, _request_context)
44
52
  ip = ip_for_match(macro_context)
45
53
  return false unless ip
46
54
  return false unless ip.ipv4? == @netblock.ipv4?
55
+
47
56
  @netblock.include?(ip)
48
57
  end
49
58
 
50
59
  def ==(other)
51
60
  return false unless other.instance_of? self.class
61
+
52
62
  netblock == other.netblock
53
63
  end
54
64
  end
@@ -32,6 +32,7 @@ module Coppertone
32
32
  def check_a_record_limit(request_context, count)
33
33
  limit = request_context.dns_lookups_per_mx_mechanism_limit
34
34
  return unless limit && count > limit
35
+
35
36
  raise Coppertone::MXLimitExceededError
36
37
  end
37
38
 
@@ -14,10 +14,11 @@ module Coppertone
14
14
  class_builder.register(klass.label, klass)
15
15
  end
16
16
 
17
- MODIFIER_REGEXP = /\A([a-zA-Z]+[a-zA-Z0-9\-\_\.]*)=(\S*)\z/
17
+ MODIFIER_REGEXP = /\A([a-zA-Z]+[a-zA-Z0-9\-_.]*)=(\S*)\z/.freeze
18
18
  def self.matching_term(text)
19
19
  matches = MODIFIER_REGEXP.match(text)
20
20
  return nil unless matches
21
+
21
22
  type = matches[1]
22
23
  attributes = matches[2]
23
24
  build(type, attributes) || build_unknown(type, attributes)
@@ -28,6 +29,7 @@ module Coppertone
28
29
  end
29
30
 
30
31
  attr_reader :arguments
32
+
31
33
  def initialize(arguments)
32
34
  @arguments = arguments
33
35
  end
@@ -3,17 +3,21 @@ module Coppertone
3
3
  # Base class including logic common to modifiers
4
4
  class Base < Modifier
5
5
  attr_reader :domain_spec
6
+
6
7
  def initialize(attributes)
7
8
  super(attributes)
8
9
  raise InvalidModifierError if attributes.blank?
10
+
9
11
  @domain_spec = Coppertone::DomainSpec.new(attributes)
10
12
  rescue Coppertone::MacroStringParsingError
11
13
  raise Coppertone::InvalidModifierError
12
14
  end
13
15
 
14
16
  def target_name_from_domain_spec(macro_context, request_context)
15
- domain =
16
- domain_spec.expand(macro_context, request_context) if domain_spec
17
+ if domain_spec
18
+ domain =
19
+ domain_spec.expand(macro_context, request_context)
20
+ end
17
21
  Coppertone::Utils::DomainUtils.macro_expanded_domain(domain)
18
22
  end
19
23
 
@@ -27,6 +31,7 @@ module Coppertone
27
31
 
28
32
  def ==(other)
29
33
  return false unless other.instance_of? self.class
34
+
30
35
  domain_spec == other.domain_spec
31
36
  end
32
37
  end
@@ -8,15 +8,18 @@ module Coppertone
8
8
  new(attributes)
9
9
  end
10
10
 
11
- ASCII_REGEXP = /\A[[:ascii:]]*\z/
11
+ ASCII_REGEXP = /\A[[:ascii:]]*\z/.freeze
12
12
  def evaluate(macro_context, request_context)
13
13
  target_name =
14
14
  target_name_from_domain_spec(macro_context, request_context)
15
15
  return nil unless target_name
16
+
16
17
  macro_string = lookup_macro_string(target_name, request_context)
17
18
  return nil unless macro_string
19
+
18
20
  expanded = macro_string.expand(macro_context, request_context)
19
- return nil unless ASCII_REGEXP.match(expanded)
21
+ return nil unless ASCII_REGEXP.match?(expanded)
22
+
20
23
  expanded
21
24
  rescue DNSAdapter::Error
22
25
  nil
@@ -27,6 +30,7 @@ module Coppertone
27
30
  request_context.dns_client.fetch_txt_records(target_name)
28
31
  return nil if records.empty?
29
32
  return nil if records.size > 1
33
+
30
34
  MacroString.new(records.first[:text])
31
35
  end
32
36
 
@@ -19,6 +19,7 @@ module Coppertone
19
19
 
20
20
  def target_domain
21
21
  raise NeedsContextError if context_dependent?
22
+
22
23
  arguments
23
24
  end
24
25
 
@@ -4,6 +4,7 @@ module Coppertone
4
4
  # defined by the SPF spec
5
5
  class Unknown < Modifier
6
6
  attr_reader :label
7
+
7
8
  def initialize(label, attributes)
8
9
  super(attributes)
9
10
  @label = label
@@ -3,7 +3,7 @@ module Coppertone
3
3
  # do not have contextual dependence.
4
4
  class NullMacroContext
5
5
  RESERVED_REGEXP = Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
6
- %w(s l o d i p v h c r t).each do |m|
6
+ %w[s l o d i p v h c r t].each do |m|
7
7
  define_method(m.upcase) do
8
8
  raise ArgumentError
9
9
  end
@@ -22,6 +22,7 @@ module Coppertone
22
22
  end
23
23
 
24
24
  attr_reader :text, :result_code
25
+
25
26
  def initialize(text, result_code)
26
27
  @text = text
27
28
  @result_code = result_code
@@ -3,6 +3,7 @@ module Coppertone
3
3
  # record from a text string.
4
4
  class Record
5
5
  attr_reader :text
6
+
6
7
  def initialize(raw_text)
7
8
  @terms = Coppertone::RecordTermParser.new(raw_text).terms
8
9
  normalize_terms
@@ -67,6 +68,7 @@ module Coppertone
67
68
 
68
69
  def netblocks_only?
69
70
  return false if redirect
71
+
70
72
  directives.reject(&:all?).reject do |d|
71
73
  d.mechanism.is_a?(Coppertone::Mechanism::IPMechanism)
72
74
  end.empty?
@@ -74,7 +76,8 @@ module Coppertone
74
76
 
75
77
  def context_dependent_evaluation?
76
78
  return true if directives.any?(&:context_dependent?)
77
- redirect && redirect.context_dependent?
79
+
80
+ redirect&.context_dependent?
78
81
  end
79
82
 
80
83
  def exp
@@ -82,7 +85,7 @@ module Coppertone
82
85
  end
83
86
 
84
87
  def context_dependent_explanation?
85
- exp && exp.context_dependent?
88
+ exp&.context_dependent?
86
89
  end
87
90
 
88
91
  KNOWN_MODS =
@@ -95,6 +98,7 @@ module Coppertone
95
98
  def find_modifier(klass)
96
99
  arr = modifiers.select { |m| m.is_a?(klass) }
97
100
  raise DuplicateModifierError if arr.size > 1
101
+
98
102
  arr.first
99
103
  end
100
104
 
@@ -2,6 +2,7 @@ module Coppertone
2
2
  # A helper class for finding SPF records for a domain.
3
3
  class RecordEvaluator
4
4
  attr_reader :record
5
+
5
6
  def initialize(record)
6
7
  @record = record
7
8
  end
@@ -9,6 +10,7 @@ module Coppertone
9
10
  def evaluate(macro_context, request_context)
10
11
  result = directive_result(macro_context, request_context)
11
12
  return result unless result.none? || result.fail?
13
+
12
14
  if result.fail?
13
15
  evaluate_fail_result(result, macro_context, request_context)
14
16
  else
@@ -30,6 +32,7 @@ module Coppertone
30
32
  def add_exp_to_result(result, macro_context, request_context)
31
33
  result = add_default_exp(result)
32
34
  return result unless record.exp
35
+
33
36
  computed_exp = record.exp.evaluate(macro_context, request_context)
34
37
  result.explanation = computed_exp if computed_exp
35
38
  result
@@ -50,10 +53,12 @@ module Coppertone
50
53
 
51
54
  def evaluate_none_result(result, macro_context, request_context)
52
55
  return result unless follow_redirect?
56
+
53
57
  finder =
54
58
  Coppertone::RedirectRecordFinder.new(record.redirect, macro_context,
55
59
  request_context)
56
60
  raise InvalidRedirectError unless finder.target && finder.record
61
+
57
62
  rc = macro_context.with_domain(finder.target)
58
63
  RecordEvaluator.new(finder.record).evaluate(rc, request_context)
59
64
  end
@@ -2,6 +2,7 @@ module Coppertone
2
2
  # A helper class for finding SPF records for a domain.
3
3
  class RecordFinder
4
4
  attr_reader :dns_client, :domain
5
+
5
6
  def initialize(dns_client, domain)
6
7
  @dns_client = dns_client
7
8
  @domain = domain
@@ -12,8 +13,7 @@ module Coppertone
12
13
  begin
13
14
  validate_txt_records
14
15
  spf_dns_record = txt_records.first
15
- return nil unless spf_dns_record
16
- Record.new(spf_dns_record)
16
+ spf_dns_record ? Record.new(spf_dns_record) : nil
17
17
  end
18
18
  end
19
19
 
@@ -2,24 +2,28 @@ module Coppertone
2
2
  # Parses a record into terms
3
3
  class RecordTermParser
4
4
  VERSION_STR = 'v=spf1'.freeze
5
- RECORD_REGEXP = /\A#{VERSION_STR}(\s|\z)/i
6
- ALLOWED_CHARACTERS = /\A([\x21-\x7e ]+)\z/
5
+ RECORD_REGEXP = /\A#{VERSION_STR}(\s|\z)/i.freeze
6
+ ALLOWED_CHARACTERS = /\A([\x21-\x7e ]+)\z/.freeze
7
7
 
8
8
  def self.record?(text)
9
9
  return false if text.blank?
10
- RECORD_REGEXP.match(text.strip) ? true : false
10
+ return true if RECORD_REGEXP.match?(text.strip)
11
+
12
+ false
11
13
  end
12
14
 
13
15
  attr_reader :text, :terms
16
+
14
17
  def initialize(text)
15
18
  raise RecordParsingError unless self.class.record?(text)
16
- raise RecordParsingError unless ALLOWED_CHARACTERS.match(text)
19
+ raise RecordParsingError unless ALLOWED_CHARACTERS.match?(text)
20
+
17
21
  @text = text
18
22
  @terms = Coppertone::TermsParser.new(terms_segment).terms
19
23
  end
20
24
 
21
25
  def terms_segment
22
- text[VERSION_STR.length..-1].strip
26
+ text[VERSION_STR.length..].strip
23
27
  end
24
28
  end
25
29
  end