coppertone 0.0.6 → 0.0.7

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +20 -0
  3. data/.travis.yml +5 -8
  4. data/README.md +4 -4
  5. data/Rakefile +6 -6
  6. data/coppertone.gemspec +2 -2
  7. data/lib/coppertone/directive.rb +1 -1
  8. data/lib/coppertone/domain_spec.rb +3 -3
  9. data/lib/coppertone/ip_address_wrapper.rb +8 -8
  10. data/lib/coppertone/macro_context.rb +6 -5
  11. data/lib/coppertone/macro_string/macro_expand.rb +9 -9
  12. data/lib/coppertone/macro_string/macro_parser.rb +5 -5
  13. data/lib/coppertone/macro_string/macro_static_expand.rb +1 -1
  14. data/lib/coppertone/macro_string.rb +1 -1
  15. data/lib/coppertone/mechanism/a.rb +1 -1
  16. data/lib/coppertone/mechanism/all.rb +2 -2
  17. data/lib/coppertone/mechanism/cidr_parser.rb +3 -2
  18. data/lib/coppertone/mechanism/domain_spec_mechanism.rb +4 -3
  19. data/lib/coppertone/mechanism/domain_spec_optional.rb +3 -2
  20. data/lib/coppertone/mechanism/domain_spec_required.rb +4 -3
  21. data/lib/coppertone/mechanism/domain_spec_with_dual_cidr.rb +10 -8
  22. data/lib/coppertone/mechanism/exists.rb +1 -1
  23. data/lib/coppertone/mechanism/include.rb +3 -3
  24. data/lib/coppertone/mechanism/include_matcher.rb +4 -4
  25. data/lib/coppertone/mechanism/ip4.rb +1 -1
  26. data/lib/coppertone/mechanism/ip6.rb +1 -1
  27. data/lib/coppertone/mechanism/ip_mechanism.rb +9 -9
  28. data/lib/coppertone/mechanism/mx.rb +2 -2
  29. data/lib/coppertone/mechanism/ptr.rb +3 -3
  30. data/lib/coppertone/mechanism.rb +1 -1
  31. data/lib/coppertone/modifier/base.rb +3 -2
  32. data/lib/coppertone/modifier/exp.rb +2 -1
  33. data/lib/coppertone/modifier/redirect.rb +3 -2
  34. data/lib/coppertone/modifier/unknown.rb +3 -1
  35. data/lib/coppertone/null_macro_context.rb +2 -2
  36. data/lib/coppertone/record.rb +6 -4
  37. data/lib/coppertone/record_evaluator.rb +1 -1
  38. data/lib/coppertone/record_finder.rb +2 -2
  39. data/lib/coppertone/record_term_parser.rb +3 -3
  40. data/lib/coppertone/request.rb +12 -2
  41. data/lib/coppertone/request_count_limiter.rb +1 -1
  42. data/lib/coppertone/sender_identity.rb +4 -4
  43. data/lib/coppertone/terms_parser.rb +1 -1
  44. data/lib/coppertone/utils/domain_utils.rb +14 -5
  45. data/lib/coppertone/utils/ip_in_domain_checker.rb +4 -1
  46. data/lib/coppertone/utils/validated_domain_finder.rb +1 -1
  47. data/lib/coppertone/version.rb +2 -2
  48. data/lib/coppertone.rb +1 -0
  49. data/spec/mechanism/a_spec.rb +9 -9
  50. data/spec/mechanism/exists_spec.rb +5 -5
  51. data/spec/mechanism/ip6_spec.rb +0 -1
  52. data/spec/open_spf/ALL_mechanism_syntax_spec.rb +0 -1
  53. data/spec/open_spf/A_mechanism_syntax_spec.rb +0 -1
  54. data/spec/open_spf/EXISTS_mechanism_syntax_spec.rb +0 -1
  55. data/spec/open_spf/IP4_mechanism_syntax_spec.rb +0 -1
  56. data/spec/open_spf/IP6_mechanism_syntax_spec.rb +0 -1
  57. data/spec/open_spf/Include_mechanism_semantics_and_syntax_spec.rb +0 -1
  58. data/spec/open_spf/Initial_processing_spec.rb +0 -1
  59. data/spec/open_spf/MX_mechanism_syntax_spec.rb +0 -1
  60. data/spec/open_spf/Macro_expansion_rules_spec.rb +0 -1
  61. data/spec/open_spf/PTR_mechanism_syntax_spec.rb +0 -1
  62. data/spec/open_spf/Processing_limits_spec.rb +0 -1
  63. data/spec/open_spf/Record_evaluation_spec.rb +0 -1
  64. data/spec/open_spf/Record_lookup_spec.rb +0 -1
  65. data/spec/open_spf/Selecting_records_spec.rb +0 -1
  66. data/spec/open_spf/Semantics_of_exp_and_other_modifiers_spec.rb +0 -1
  67. data/spec/open_spf/Test_cases_from_implementation_bugs_spec.rb +0 -1
  68. data/spec/record_spec.rb +5 -5
  69. data/spec/utils/domain_utils_spec.rb +69 -5
  70. metadata +4 -3
@@ -17,7 +17,7 @@ module Coppertone
17
17
  end
18
18
 
19
19
  def self.register(klass)
20
- fail ArgumentError unless klass < self
20
+ raise ArgumentError unless klass < self
21
21
  class_builder.register(klass.label, klass)
22
22
  end
23
23
 
@@ -1,10 +1,11 @@
1
1
  module Coppertone
2
- class Modifier # rubocop:disable Style/Documentation
2
+ class Modifier
3
+ # Base class including logic common to modifiers
3
4
  class Base < Modifier
4
5
  attr_reader :domain_spec
5
6
  def initialize(attributes)
6
7
  super(attributes)
7
- fail InvalidModifierError if attributes.blank?
8
+ raise InvalidModifierError if attributes.blank?
8
9
  @domain_spec = Coppertone::DomainSpec.new(attributes)
9
10
  rescue Coppertone::MacroStringParsingError
10
11
  raise Coppertone::InvalidModifierError
@@ -1,7 +1,8 @@
1
1
  require 'coppertone/modifier/base'
2
2
 
3
3
  module Coppertone
4
- class Modifier # rubocop:disable Style/Documentation
4
+ class Modifier
5
+ # Exp modifier - specifying a message to be returned in case of failure
5
6
  class Exp < Coppertone::Modifier::Base
6
7
  def self.create(attributes)
7
8
  new(attributes)
@@ -1,7 +1,8 @@
1
1
  require 'coppertone/modifier/base'
2
2
 
3
3
  module Coppertone
4
- class Modifier # rubocop:disable Style/Documentation
4
+ class Modifier
5
+ # A Redirect modifier found in an SPF record.
5
6
  class Redirect < Coppertone::Modifier::Base
6
7
  def self.create(attributes)
7
8
  new(attributes)
@@ -17,7 +18,7 @@ module Coppertone
17
18
  end
18
19
 
19
20
  def target_domain
20
- fail NeedsContextError if context_dependent?
21
+ raise NeedsContextError if context_dependent?
21
22
  arguments
22
23
  end
23
24
 
@@ -1,5 +1,7 @@
1
1
  module Coppertone
2
- class Modifier # rubocop:disable Style/Documentation
2
+ class Modifier
3
+ # Object representing unknown modifiers - those that aren't explicitly
4
+ # defined by the SPF spec
3
5
  class Unknown < Modifier
4
6
  attr_reader :label
5
7
  def initialize(label, attributes)
@@ -5,11 +5,11 @@ module Coppertone
5
5
  RESERVED_REGEXP = Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
6
6
  %w(s l o d i p v h c r t).each do |m|
7
7
  define_method(m.upcase) do
8
- fail ArgumentError
8
+ raise ArgumentError
9
9
  end
10
10
 
11
11
  define_method(m) do
12
- fail ArgumentError
12
+ raise ArgumentError
13
13
  end
14
14
  end
15
15
 
@@ -33,7 +33,7 @@ module Coppertone
33
33
  @dns_lookup_term_count ||=
34
34
  begin
35
35
  base = redirect.nil? ? 0 : 1
36
- base + directives.select(&:dns_lookup_term?).size
36
+ base + directives.count(&:dns_lookup_term?)
37
37
  end
38
38
  end
39
39
 
@@ -60,7 +60,9 @@ module Coppertone
60
60
 
61
61
  def netblock_mechanisms
62
62
  @netblock_mechanisms ||=
63
- directives.select { |d| d.mechanism.is_a?(Coppertone::Mechanism::IPMechanism) }
63
+ directives.select do |d|
64
+ d.mechanism.is_a?(Coppertone::Mechanism::IPMechanism)
65
+ end
64
66
  end
65
67
 
66
68
  def netblocks_only?
@@ -84,7 +86,7 @@ module Coppertone
84
86
  end
85
87
 
86
88
  KNOWN_MODS =
87
- [Coppertone::Modifier::Exp, Coppertone::Modifier::Redirect]
89
+ [Coppertone::Modifier::Exp, Coppertone::Modifier::Redirect].freeze
88
90
  def unknown_modifiers
89
91
  @unknown_modifiers ||=
90
92
  modifiers.select { |m| KNOWN_MODS.select { |k| m.is_a?(k) }.empty? }
@@ -92,7 +94,7 @@ module Coppertone
92
94
 
93
95
  def find_modifier(klass)
94
96
  arr = modifiers.select { |m| m.is_a?(klass) }
95
- fail DuplicateModifierError if arr.size > 1
97
+ raise DuplicateModifierError if arr.size > 1
96
98
  arr.first
97
99
  end
98
100
 
@@ -53,7 +53,7 @@ module Coppertone
53
53
  finder =
54
54
  Coppertone::RedirectRecordFinder.new(record.redirect, macro_context,
55
55
  request_context)
56
- fail InvalidRedirectError unless finder.target && finder.record
56
+ raise InvalidRedirectError unless finder.target && finder.record
57
57
  rc = macro_context.with_domain(finder.target)
58
58
  RecordEvaluator.new(finder.record).evaluate(rc, request_context)
59
59
  end
@@ -22,7 +22,7 @@ module Coppertone
22
22
  begin
23
23
  if Coppertone::Utils::DomainUtils.valid?(domain)
24
24
  dns_client.fetch_txt_records(domain).map { |r| r[:text] }
25
- .select { |r| Record.record?(r) }
25
+ .select { |r| Record.record?(r) }
26
26
  else
27
27
  []
28
28
  end
@@ -30,7 +30,7 @@ module Coppertone
30
30
  end
31
31
 
32
32
  def validate_txt_records
33
- fail AmbiguousSpfRecordError if txt_records.size > 1
33
+ raise AmbiguousSpfRecordError if txt_records.size > 1
34
34
  end
35
35
  end
36
36
  end
@@ -1,7 +1,7 @@
1
1
  module Coppertone
2
2
  # Parses a record into terms
3
3
  class RecordTermParser
4
- VERSION_STR = 'v=spf1'
4
+ VERSION_STR = 'v=spf1'.freeze
5
5
  RECORD_REGEXP = /\A#{VERSION_STR}(\s|\z)/i
6
6
  ALLOWED_CHARACTERS = /\A([\x21-\x7e ]+)\z/
7
7
 
@@ -12,8 +12,8 @@ module Coppertone
12
12
 
13
13
  attr_reader :text, :terms
14
14
  def initialize(text)
15
- fail RecordParsingError unless self.class.record?(text)
16
- fail RecordParsingError unless ALLOWED_CHARACTERS.match(text)
15
+ raise RecordParsingError unless self.class.record?(text)
16
+ raise RecordParsingError unless ALLOWED_CHARACTERS.match(text)
17
17
  @text = text
18
18
  @terms = Coppertone::TermsParser.new(terms_segment).terms
19
19
  end
@@ -11,12 +11,22 @@ module Coppertone
11
11
  end
12
12
 
13
13
  def authenticate
14
+ process_helo || process_mailfrom || default_value
15
+ end
16
+
17
+ def process_helo
14
18
  check_spf_for_helo
15
- return helo_result if helo_result && !helo_result.none?
19
+ return nil if helo_result && helo_result.none?
20
+ helo_result
21
+ end
16
22
 
23
+ def process_mailfrom
17
24
  check_spf_for_mailfrom
18
- return mailfrom_result if mailfrom_result && !mailfrom_result.none?
25
+ return nil if mailfrom_result && mailfrom_result.none?
26
+ mailfrom_result
27
+ end
19
28
 
29
+ def default_value
20
30
  no_matching_record? ? Result.none : Result.neutral
21
31
  end
22
32
 
@@ -17,7 +17,7 @@ module Coppertone
17
17
 
18
18
  def check_if_limit_exceeded
19
19
  return if limit.nil?
20
- fail Coppertone::LimitExceededError, exception_message if exceeded?
20
+ raise Coppertone::LimitExceededError, exception_message if exceeded?
21
21
  end
22
22
 
23
23
  def exception_message
@@ -3,7 +3,7 @@ module Coppertone
3
3
  # Parses the identity and ensures validity. Also has accessor methods
4
4
  # for the macro letters.
5
5
  class SenderIdentity
6
- DEFAULT_LOCALPART = 'postmaster'
6
+ DEFAULT_LOCALPART = 'postmaster'.freeze
7
7
  EMAIL_ADDRESS_SPLIT_REGEXP = /^(.*)@(.*?)$/
8
8
 
9
9
  attr_reader :sender, :localpart, :domain
@@ -12,9 +12,9 @@ module Coppertone
12
12
  initialize_localpart_and_domain
13
13
  end
14
14
 
15
- alias_method :s, :sender
16
- alias_method :l, :localpart
17
- alias_method :o, :domain
15
+ alias s sender
16
+ alias l localpart
17
+ alias o domain
18
18
 
19
19
  private
20
20
 
@@ -16,7 +16,7 @@ module Coppertone
16
16
 
17
17
  def parse_token(token)
18
18
  term = Term.build_from_token(token)
19
- fail RecordParsingError unless term
19
+ raise RecordParsingError unless term
20
20
  term
21
21
  end
22
22
  end
@@ -11,6 +11,7 @@ module Coppertone
11
11
  return false if labels.length <= 1
12
12
  return false if domain.length > 253
13
13
  return false if labels.any? { |l| !valid_label?(l) }
14
+ return false unless valid_tld?(labels.last)
14
15
  true
15
16
  end
16
17
 
@@ -22,18 +23,19 @@ module Coppertone
22
23
  labels = to_labels(domain)
23
24
  return '.' if labels.size == 1
24
25
  labels.shift
25
- return labels.join('.')
26
+ labels.join('.')
26
27
  end
27
28
 
28
29
  def self.to_ascii_labels(domain)
29
- Addressable::IDNA.to_ascii(domain).split('.').map { |d| d.downcase }
30
+ Addressable::IDNA.to_ascii(domain).split('.').map(&:downcase)
30
31
  end
31
32
 
32
33
  def self.normalized_domain(domain)
33
34
  to_ascii_labels(domain).join('.')
34
35
  end
35
36
 
36
- NO_DASH_REGEXP = /\A[a-zA-Z0-9]*[a-zA-Z]+[a-zA-Z0-9]*\z/
37
+ NO_DASH_NONNUMERIC_REGEXP = /\A[a-zA-Z0-9]*[a-zA-Z]+[a-zA-Z0-9]*\z/
38
+ NO_DASH_REGEXP = /\A[a-zA-Z0-9]+\z/
37
39
  DASH_REGEXP = /\A[a-zA-Z0-9]+\-[a-zA-Z0-9\-]*[a-zA-Z0-9]+\z/
38
40
 
39
41
  def self.valid_hostname_label?(l)
@@ -41,13 +43,20 @@ module Coppertone
41
43
  NO_DASH_REGEXP.match(l) || DASH_REGEXP.match(l)
42
44
  end
43
45
 
46
+ def self.valid_tld?(l)
47
+ return false unless valid_label?(l)
48
+ NO_DASH_NONNUMERIC_REGEXP.match(l) || DASH_REGEXP.match(l)
49
+ end
50
+
44
51
  def self.valid_ldh_domain?(domain)
45
52
  return false unless valid?(domain)
46
- to_ascii_labels(domain).all? { |l| valid_hostname_label?(l) }
53
+ labels = to_ascii_labels(domain)
54
+ return false unless valid_tld?(labels.last)
55
+ labels.all? { |l| valid_hostname_label?(l) }
47
56
  end
48
57
 
49
58
  def self.valid_label?(l)
50
- (l.length >= 0) && (l.length <= 63)
59
+ (l.length >= 0) && (l.length <= 63) && !l.match(/\s/)
51
60
  end
52
61
 
53
62
  def self.macro_expanded_domain(domain)
@@ -1,5 +1,8 @@
1
1
  module Coppertone
2
- module Utils # rubocop:disable Style/Documentation
2
+ module Utils
3
+ # Checks the IP address from the request against an A or AAAA record
4
+ # for a domain. Takes optional CIDR arguments so the match can
5
+ # check subnets
3
6
  class IPInDomainChecker
4
7
  def initialize(macro_context, request_context)
5
8
  @macro_context = macro_context
@@ -1,5 +1,5 @@
1
1
  module Coppertone
2
- module Utils # rubocop:disable Style/Documentation
2
+ module Utils
3
3
  # A class used to find validated domains as defined in
4
4
  # section 5.5 of the RFC.
5
5
  class ValidatedDomainFinder
@@ -1,3 +1,3 @@
1
- module Coppertone # rubocop:disable Style/Documentation
2
- VERSION = '0.0.6'.freeze
1
+ module Coppertone
2
+ VERSION = '0.0.7'.freeze
3
3
  end
data/lib/coppertone.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/object/blank'
2
+ require 'active_support/core_ext/object/try'
2
3
 
3
4
  # A library for evaluating, creating, and analyzing SPF records
4
5
  module Coppertone
@@ -199,15 +199,15 @@ describe Coppertone::Mechanism::A do
199
199
  dc = double(:dns_client)
200
200
  allow(dc).to receive(:fetch_a_records)
201
201
  .with(mech_domain).and_return([
202
- {
203
- type: 'A',
204
- address: '74.125.239.117'
205
- },
206
- {
207
- type: 'A',
208
- address: '74.125.239.118'
209
- }
210
- ])
202
+ {
203
+ type: 'A',
204
+ address: '74.125.239.117'
205
+ },
206
+ {
207
+ type: 'A',
208
+ address: '74.125.239.118'
209
+ }
210
+ ])
211
211
  dc
212
212
  end
213
213
 
@@ -84,11 +84,11 @@ describe Coppertone::Mechanism::Exists do
84
84
  dc = double(:dns_client)
85
85
  allow(dc).to receive(:fetch_a_records)
86
86
  .with(mech_domain).and_return([
87
- {
88
- type: 'A',
89
- address: '74.125.234.117'
90
- }
91
- ])
87
+ {
88
+ type: 'A',
89
+ address: '74.125.234.117'
90
+ }
91
+ ])
92
92
  allow(dc).to receive(:fetch_a_records)
93
93
  .with(bad_domain).and_return([])
94
94
  dc
@@ -45,7 +45,6 @@ describe Coppertone::Mechanism::IP6 do
45
45
  Coppertone::Mechanism::IP6.new(':fe80::202:b3ff:fe1e:8329/384')
46
46
  end.to raise_error(Coppertone::InvalidMechanismError)
47
47
  end
48
-
49
48
  end
50
49
 
51
50
  context '#create' do
@@ -34,5 +34,4 @@ describe 'ALL mechanism syntax' do
34
34
  result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e5.example.com', 'mail.example.com', options)
35
35
  expect([:pass]).to include(result.code)
36
36
  end
37
-
38
37
  end
@@ -155,5 +155,4 @@ describe 'A mechanism syntax' do
155
155
  result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e13.example.com', 'mail.example.com', options)
156
156
  expect([:permerror]).to include(result.code)
157
157
  end
158
-
159
158
  end
@@ -42,5 +42,4 @@ describe 'EXISTS mechanism syntax' do
42
42
  result = Coppertone::SpfService.authenticate_email('CAFE:BABE::3', 'foo@e6.example.com', 'mail.example.com', options)
43
43
  expect([:temperror]).to include(result.code)
44
44
  end
45
-
46
45
  end
@@ -55,5 +55,4 @@ describe 'IP4 mechanism syntax' do
55
55
  result = Coppertone::SpfService.authenticate_email('::FFFF:1.2.3.4', 'foo@e7.example.com', 'mail.example.com', options)
56
56
  expect([:fail]).to include(result.code)
57
57
  end
58
-
59
58
  end
@@ -56,5 +56,4 @@ describe 'IP6 mechanism syntax' do
56
56
  result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e6.example.com', 'mail.example.com', options)
57
57
  expect([:permerror]).to include(result.code)
58
58
  end
59
-
60
59
  end
@@ -52,5 +52,4 @@ describe 'Include mechanism semantics and syntax' do
52
52
  result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e8.example.com', 'mail.example.com', options)
53
53
  expect([:permerror]).to include(result.code)
54
54
  end
55
-
56
55
  end
@@ -80,5 +80,4 @@ describe 'Initial processing' do
80
80
  result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'actually@fine.example.com', 'hosed', options)
81
81
  expect([:fail]).to include(result.code)
82
82
  end
83
-
84
83
  end
@@ -115,5 +115,4 @@ describe 'MX mechanism syntax' do
115
115
  result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e13.example.com', 'mail.example.com', options)
116
116
  expect([:permerror]).to include(result.code)
117
117
  end
118
-
119
118
  end
@@ -146,5 +146,4 @@ describe 'Macro expansion rules' do
146
146
  result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo-bar+zip+quux@e12.example.com', 'mail.example.com', options)
147
147
  expect([:pass]).to include(result.code)
148
148
  end
149
-
150
149
  end
@@ -38,5 +38,4 @@ describe 'PTR mechanism syntax' do
38
38
  result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e5.example.com', 'mail.example.com', options)
39
39
  expect([:permerror]).to include(result.code)
40
40
  end
41
-
42
41
  end
@@ -68,5 +68,4 @@ describe 'Processing limits' do
68
68
  result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e11.example.com', 'mail.example.com', options)
69
69
  expect([:permerror]).to include(result.code)
70
70
  end
71
-
72
71
  end
@@ -71,5 +71,4 @@ describe 'Record evaluation' do
71
71
  result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@t12.example.com', '%%%%%%%%%%%%%%%%%%%%%%', options)
72
72
  expect([:fail, :permerror]).to include(result.code)
73
73
  end
74
-
75
74
  end
@@ -44,5 +44,4 @@ describe 'Record lookup' do
44
44
  result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@alltimeout.example.net', 'mail.example.net', options)
45
45
  expect([:temperror]).to include(result.code)
46
46
  end
47
-
48
47
  end
@@ -57,5 +57,4 @@ describe 'Selecting records' do
57
57
  result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@example9.com', 'mail.example1.com', options)
58
58
  expect([:softfail]).to include(result.code)
59
59
  end
60
-
61
60
  end
@@ -165,5 +165,4 @@ describe 'Semantics of exp and other modifiers' do
165
165
  result = Coppertone::SpfService.authenticate_email('192.0.2.2', 'bar@e24.example.com', 'e24.example.com', options)
166
166
  expect([:pass]).to include(result.code)
167
167
  end
168
-
169
168
  end
@@ -13,5 +13,4 @@ describe 'Test cases from implementation bugs' do
13
13
  result = Coppertone::SpfService.authenticate_email('2001:db8:ff0:100::2', 'test@example.org', 'example.org', options)
14
14
  expect([:pass]).to include(result.code)
15
15
  end
16
-
17
16
  end
data/spec/record_spec.rb CHANGED
@@ -138,9 +138,9 @@ describe Coppertone::Record do
138
138
 
139
139
  it 'should yield a set of the includes, in order' do
140
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}')
141
+ .new('v=spf1 include:_spf.domain1.com ip4:1.2.3.4 include:_spf.domain2.com ~all exp=explain._spf.%{d}')
142
142
  includes = r.includes
143
- expect(includes.map(&:mechanism).all? {|i| i.is_a?(Coppertone::Mechanism::Include)}).to be_truthy
143
+ expect(includes.map(&:mechanism).all? { |i| i.is_a?(Coppertone::Mechanism::Include) }).to be_truthy
144
144
  expect(includes.map(&:target_domain)).to eq(%w(_spf.domain1.com _spf.domain2.com))
145
145
  end
146
146
  end
@@ -148,19 +148,19 @@ describe Coppertone::Record do
148
148
  context '#context_dependent_evaluation?' do
149
149
  it 'should be false when the record is not context dependent' do
150
150
  r = Coppertone::Record
151
- .new('v=spf1 include:_spf.domain1.com ip4:1.2.3.4 include:_spf.domain2.com ~all exp=explain._spf.source.com')
151
+ .new('v=spf1 include:_spf.domain1.com ip4:1.2.3.4 include:_spf.domain2.com ~all exp=explain._spf.source.com')
152
152
  expect(r).not_to be_context_dependent_evaluation
153
153
  end
154
154
 
155
155
  it 'should be false when the record has a context dependent redirect' do
156
156
  r = Coppertone::Record
157
- .new('v=spf1 redirect=explain._spf.%{h}')
157
+ .new('v=spf1 redirect=explain._spf.%{h}')
158
158
  expect(r).to be_context_dependent_evaluation
159
159
  end
160
160
 
161
161
  it 'should be false when the record has a context dependent directive' do
162
162
  r = Coppertone::Record
163
- .new('v=spf1 include:_spf.domain1.com ip4:1.2.3.4 include:_spf.%{l}.domain2.com ~all exp=explain._spf.static.com')
163
+ .new('v=spf1 include:_spf.domain1.com ip4:1.2.3.4 include:_spf.%{l}.domain2.com ~all exp=explain._spf.static.com')
164
164
  expect(r).to be_context_dependent_evaluation
165
165
  end
166
166
  end
@@ -11,6 +11,11 @@ describe Coppertone::Utils::DomainUtils do
11
11
  expect(subject.valid?('abc.bit.ly')).to eq(true)
12
12
  end
13
13
 
14
+ it 'should validate domains with numeric labels' do
15
+ expect(subject.valid?('abc.126.com')).to eq(true)
16
+ expect(subject.valid?('37.com')).to eq(true)
17
+ end
18
+
14
19
  it 'should validate domains when they are dot-terminated' do
15
20
  expect(subject.valid?('gmail.com.')).to eq(true)
16
21
  expect(subject.valid?('fermion.mit.edu.')).to eq(true)
@@ -26,6 +31,19 @@ describe Coppertone::Utils::DomainUtils do
26
31
  expect(subject.valid?('清华大学.cn')).to eq(true)
27
32
  expect(subject.valid?('ジェーピーニック.jp')).to eq(true)
28
33
  end
34
+
35
+ it 'should reject labels containing whitespace' do
36
+ expect(subject.valid?('mail mike.net')).to eq(false)
37
+ end
38
+
39
+ it 'should validate domains with underscores' do
40
+ expect(subject.valid?('_dmarc.126.com')).to eq(true)
41
+ expect(subject.valid?('abcd._domainkey.gmail.com')).to eq(true)
42
+ end
43
+
44
+ it 'should reject IP addresses' do
45
+ expect(subject.valid?('192.38.7.14')).to eq(false)
46
+ end
29
47
  end
30
48
 
31
49
  context '#macro_expanded_domain' do
@@ -53,7 +71,7 @@ describe Coppertone::Utils::DomainUtils do
53
71
  end
54
72
 
55
73
  it 'truncates overlong domains' do
56
- domain_candidate_labels = 50.times.map { "a-#{SecureRandom.hex(2)}" }
74
+ domain_candidate_labels = Array.new(50) { "a-#{SecureRandom.hex(2)}" }
57
75
  domain_candidate = domain_candidate_labels.join('.')
58
76
  truncated_labels = domain_candidate_labels.drop(14)
59
77
  truncated_domain = truncated_labels.join('.')
@@ -65,7 +83,8 @@ describe Coppertone::Utils::DomainUtils do
65
83
 
66
84
  context '#parent_domain' do
67
85
  it 'should handle hostnames correctly' do
68
- expect(subject.parent_domain('abc.xyz.example.com')).to eq('xyz.example.com')
86
+ expect(subject.parent_domain('abc.xyz.example.com'))
87
+ .to eq('xyz.example.com')
69
88
  end
70
89
 
71
90
  it 'should handle TLDs correctly' do
@@ -75,12 +94,57 @@ describe Coppertone::Utils::DomainUtils do
75
94
 
76
95
  context '#normalized_domain' do
77
96
  it 'should handle ASCII hostnames correctly' do
78
- expect(subject.normalized_domain('abc.xyz.example.com')).to eq('abc.xyz.example.com')
79
- expect(subject.normalized_domain('ABc.xYz.exAMPle.COM')).to eq('abc.xyz.example.com')
97
+ expect(subject.normalized_domain('abc.xyz.example.com'))
98
+ .to eq('abc.xyz.example.com')
99
+ expect(subject.normalized_domain('ABc.xYz.exAMPle.COM'))
100
+ .to eq('abc.xyz.example.com')
80
101
  end
81
102
 
82
103
  it 'should handle Unicode domains correctly' do
83
- expect(subject.normalized_domain('FERMIon.清华大学.cn')).to eq('fermion.xn--xkry9kk1bz66a.cn')
104
+ expect(subject.normalized_domain('FERMIon.清华大学.cn'))
105
+ .to eq('fermion.xn--xkry9kk1bz66a.cn')
106
+ end
107
+ end
108
+
109
+ context '#valid_ldh_domain?' do
110
+ it 'should validate standard domains' do
111
+ expect(subject.valid_ldh_domain?('gmail.com')).to eq(true)
112
+ expect(subject.valid_ldh_domain?('fermion.mit.edu')).to eq(true)
113
+ expect(subject.valid_ldh_domain?('abc.bit.ly')).to eq(true)
114
+ end
115
+
116
+ it 'should validate domains with numeric labels' do
117
+ expect(subject.valid_ldh_domain?('abc.126.com')).to eq(true)
118
+ expect(subject.valid_ldh_domain?('37.com')).to eq(true)
119
+ end
120
+
121
+ it 'should validate domains when they are dot-terminated' do
122
+ expect(subject.valid_ldh_domain?('gmail.com.')).to eq(true)
123
+ expect(subject.valid_ldh_domain?('fermion.mit.edu.')).to eq(true)
124
+ expect(subject.valid_ldh_domain?('abc.bit.ly.')).to eq(true)
125
+ end
126
+
127
+ it 'should reject domains with less than two labels' do
128
+ expect(subject.valid_ldh_domain?('')).to eq(false)
129
+ expect(subject.valid_ldh_domain?('one')).to eq(false)
130
+ end
131
+
132
+ it 'should handle IDNA domains' do
133
+ expect(subject.valid_ldh_domain?('清华大学.cn')).to eq(true)
134
+ expect(subject.valid_ldh_domain?('ジェーピーニック.jp')).to eq(true)
135
+ end
136
+
137
+ it 'should reject labels containing whitespace' do
138
+ expect(subject.valid_ldh_domain?('mail mike.net')).to eq(false)
139
+ end
140
+
141
+ it 'should reject domains with underscores' do
142
+ expect(subject.valid_ldh_domain?('_dmarc.126.com')).to eq(false)
143
+ expect(subject.valid_ldh_domain?('abcd._domainkey.gmail.com')).to eq(false)
144
+ end
145
+
146
+ it 'should reject IP addresses' do
147
+ expect(subject.valid_ldh_domain?('192.38.7.14')).to eq(false)
84
148
  end
85
149
  end
86
150
  end