coppertone 0.0.7 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
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