coppertone 0.0.6 → 0.0.7

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