coppertone 0.0.1 → 0.0.2

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/LICENSE +1 -1
  4. data/Rakefile +2 -2
  5. data/coppertone.gemspec +1 -0
  6. data/lib/coppertone/directive.rb +14 -7
  7. data/lib/coppertone/error.rb +24 -5
  8. data/lib/coppertone/ip_address_wrapper.rb +9 -6
  9. data/lib/coppertone/macro_context.rb +7 -13
  10. data/lib/coppertone/macro_string/macro_expand.rb +3 -2
  11. data/lib/coppertone/macro_string.rb +12 -5
  12. data/lib/coppertone/mechanism/a.rb +5 -1
  13. data/lib/coppertone/mechanism/all.rb +11 -4
  14. data/lib/coppertone/mechanism/cidr_parser.rb +1 -1
  15. data/lib/coppertone/mechanism/domain_spec_mechanism.rb +9 -0
  16. data/lib/coppertone/mechanism/domain_spec_optional.rb +1 -0
  17. data/lib/coppertone/mechanism/domain_spec_required.rb +1 -0
  18. data/lib/coppertone/mechanism/domain_spec_with_dual_cidr.rb +1 -0
  19. data/lib/coppertone/mechanism/exists.rb +5 -1
  20. data/lib/coppertone/mechanism/include.rb +17 -5
  21. data/lib/coppertone/mechanism/ip4.rb +5 -1
  22. data/lib/coppertone/mechanism/ip6.rb +5 -1
  23. data/lib/coppertone/mechanism/ip_mechanism.rb +9 -1
  24. data/lib/coppertone/mechanism/mx.rb +5 -1
  25. data/lib/coppertone/mechanism/ptr.rb +5 -1
  26. data/lib/coppertone/mechanism.rb +25 -2
  27. data/lib/coppertone/modifier/base.rb +1 -0
  28. data/lib/coppertone/modifier/exp.rb +6 -2
  29. data/lib/coppertone/modifier/redirect.rb +5 -1
  30. data/lib/coppertone/modifier/unknown.rb +6 -1
  31. data/lib/coppertone/modifier.rb +11 -2
  32. data/lib/coppertone/null_macro_context.rb +23 -0
  33. data/lib/coppertone/qualifier.rb +8 -0
  34. data/lib/coppertone/record.rb +33 -41
  35. data/lib/coppertone/record_finder.rb +3 -1
  36. data/lib/coppertone/record_term_parser.rb +30 -0
  37. data/lib/coppertone/request.rb +3 -1
  38. data/lib/coppertone/request_context.rb +1 -2
  39. data/lib/coppertone/result.rb +6 -2
  40. data/lib/coppertone/utils/validated_domain_finder.rb +6 -4
  41. data/lib/coppertone/version.rb +1 -1
  42. data/lib/coppertone.rb +3 -1
  43. data/spec/directive_spec.rb +13 -0
  44. data/spec/ip_address_wrapper_spec.rb +3 -0
  45. data/spec/macro_string_spec.rb +20 -0
  46. data/spec/mechanism/a_spec.rb +22 -7
  47. data/spec/mechanism/all_spec.rb +8 -0
  48. data/spec/mechanism/exists_spec.rb +14 -6
  49. data/spec/mechanism/include_spec.rb +13 -1
  50. data/spec/mechanism/ip4_spec.rb +15 -5
  51. data/spec/mechanism/ip6_spec.rb +18 -11
  52. data/spec/mechanism/mx_spec.rb +56 -0
  53. data/spec/mechanism/ptr_spec.rb +7 -0
  54. data/spec/modifier/exp_spec.rb +10 -0
  55. data/spec/modifier/redirect_spec.rb +10 -0
  56. data/spec/open_spf/ALL_mechanism_syntax_spec.rb +6 -6
  57. data/spec/open_spf/A_mechanism_syntax_spec.rb +30 -30
  58. data/spec/open_spf/EXISTS_mechanism_syntax_spec.rb +8 -8
  59. data/spec/open_spf/IP4_mechanism_syntax_spec.rb +10 -10
  60. data/spec/open_spf/IP6_mechanism_syntax_spec.rb +10 -10
  61. data/spec/open_spf/Include_mechanism_semantics_and_syntax_spec.rb +10 -10
  62. data/spec/open_spf/Initial_processing_spec.rb +21 -14
  63. data/spec/open_spf/MX_mechanism_syntax_spec.rb +22 -22
  64. data/spec/open_spf/Macro_expansion_rules_spec.rb +26 -30
  65. data/spec/open_spf/PTR_mechanism_syntax_spec.rb +7 -7
  66. data/spec/open_spf/Processing_limits_spec.rb +12 -12
  67. data/spec/open_spf/Record_evaluation_spec.rb +13 -13
  68. data/spec/open_spf/Record_lookup_spec.rb +8 -8
  69. data/spec/open_spf/Selecting_records_spec.rb +11 -11
  70. data/spec/open_spf/Semantics_of_exp_and_other_modifiers_spec.rb +27 -25
  71. data/spec/open_spf/Test_cases_from_implementation_bugs_spec.rb +2 -2
  72. data/spec/record_spec.rb +34 -13
  73. data/spec/request_context_spec.rb +1 -1
  74. data/spec/rfc7208-tests.yml +10 -0
  75. metadata +59 -48
  76. data/lib/coppertone/dns/error.rb +0 -9
  77. data/lib/coppertone/dns/mock_client.rb +0 -106
  78. data/lib/coppertone/dns/resolv_client.rb +0 -110
  79. data/lib/coppertone/dns.rb +0 -3
  80. data/lib/resolv/dns/resource/in/spf.rb +0 -15
  81. data/spec/dns/resolv_client_spec.rb +0 -307
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3bcbc913e28d44152b2f68d954533ec5ece2d454
4
- data.tar.gz: f2d0f7ab141c086ceb8476e7e7c091185d3fd67a
3
+ metadata.gz: 70afc7cde39b4b1374dd4b0a580b9908a15b8745
4
+ data.tar.gz: ef5687a65195e0c2291f68bad0671b5448b35af6
5
5
  SHA512:
6
- metadata.gz: 128de64f9739785af238106d2ed7b8297a3bc98a41e58193833f8c4ec07dcfa402d847de56a4fccc0d4347a437fbbcd1f00f2d276ae93a06fde290dbf4e9746a
7
- data.tar.gz: 5e8e211d963dc5f1135a8e870e3b176f9349a220c1d0eac4822c5c0884ed8f91353728b71d8169e17a44352ec7bfb052a11cc8a18b6033402cd247ac42283954
6
+ metadata.gz: f441c6616fe2346f55fa5251c74a15c5f77701684d612441a918572da57675a89b1cb956a4c7aee006e59f496dfd33b4378a133005eb703a66dbee9f7dde0ce4
7
+ data.tar.gz: c9b568748213933e23c29ca8028cf96b8b179357cd02c0c7915dd05bba7adf033a5005be97f2677de9f70e6fb8afa17192765b59bb4251de1fd9410662c0b4d1
data/.travis.yml CHANGED
@@ -4,4 +4,5 @@ env:
4
4
  rvm:
5
5
  - 2.0.0
6
6
  - 2.1.2
7
+ - jruby-19mode
7
8
 
data/LICENSE CHANGED
@@ -186,7 +186,7 @@ Apache License
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright {yyyy} {name of copyright owner}
189
+ Copyright 2014 Peter M. Goldstein
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
data/Rakefile CHANGED
@@ -48,7 +48,7 @@ def write_zonedata(f, zonedata, indent = 1)
48
48
  puts_prefixed_string(f, zonedata, indent + 1)
49
49
  puts_prefixed_string(f, 'end', indent)
50
50
  empty_line(f)
51
- dns_line = 'let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }'
51
+ dns_line = 'let(:dns_client) { DNSAdapter::MockClient.new(zonefile) }'
52
52
  puts_prefixed_string(f, dns_line, indent)
53
53
  puts_prefixed_string(f, 'let(:options) { { dns_client: dns_client } }',
54
54
  indent)
@@ -85,7 +85,7 @@ def write_result(f, host, mailfrom, helo, indent)
85
85
  end
86
86
 
87
87
  def write_expects(f, results, explanation, indent)
88
- results_array = "%i(#{results.join(' ')})"
88
+ results_array = "[#{results.map { |r| ':' + r } .join(',')}]"
89
89
  code_expect = "expect(#{results_array}).to include(result.code)"
90
90
  puts_prefixed_string(f, code_expect, indent)
91
91
  return unless explanation
data/coppertone.gemspec CHANGED
@@ -17,6 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(/^(test|spec|features)\//)
18
18
  spec.require_paths = ['lib']
19
19
 
20
+ spec.add_runtime_dependency 'dns_adapter'
20
21
  spec.add_runtime_dependency 'i18n'
21
22
  spec.add_runtime_dependency 'addressable'
22
23
  spec.add_runtime_dependency 'activesupport', '>= 3.0'
@@ -12,13 +12,20 @@ module Coppertone
12
12
  end
13
13
 
14
14
  def evaluate(context, options)
15
- code =
16
- if mechanism.match?(context, options)
17
- qualifier.result_code
18
- else
19
- Result::NONE
20
- end
21
- Coppertone::Result.from_directive(self, code)
15
+ if mechanism.match?(context, options)
16
+ Coppertone::Result.from_directive(self)
17
+ else
18
+ Result.none
19
+ end
20
+ end
21
+
22
+ def all?
23
+ mechanism.is_a?(Coppertone::Mechanism::All)
24
+ end
25
+
26
+ def to_s
27
+ mechanism_s = mechanism.to_s
28
+ qualifier.default? ? mechanism_s : "#{qualifier}#{mechanism_s}"
22
29
  end
23
30
 
24
31
  DIRECTIVE_REGEXP = /\A([\+\-\~\?]?)([a-zA-Z0-9]*)((:?)\S*)\z/
@@ -2,26 +2,45 @@ module Coppertone
2
2
  class Error < ::StandardError
3
3
  end
4
4
 
5
+ # Error classes mapping to the SPF result codes
5
6
  class TemperrorError < Coppertone::Error; end
6
7
  class PermerrorError < Coppertone::Error; end
7
8
 
8
- class InvalidSenderError < Coppertone::Error; end
9
- class MissingNameError < Coppertone::PermerrorError; end
10
- class MissingQualifierError < Coppertone::PermerrorError; end
11
-
9
+ # Errors occurring when the string representation of a MacroString
10
+ # or DomainSpec does not obey the syntax requirements.
12
11
  class MacroStringParsingError < Coppertone::PermerrorError; end
13
12
  class DomainSpecParsingError < MacroStringParsingError; end
14
13
 
14
+ # Occurs when an SPF record cannot be parsed.
15
15
  class RecordParsingError < Coppertone::PermerrorError; end
16
+
17
+ # Occurs when an individual mechanism cannot be parsed, usually
18
+ # because the arguments passed to the mechanism are not syntactically
19
+ # valid.
16
20
  class InvalidMechanismError < Coppertone::RecordParsingError; end
21
+
22
+ # Occurs when an individual modifier cannot be parsed, usually
23
+ # because the arguments passed to the modifier are not syntactically
24
+ # valid.
17
25
  class InvalidModifierError < Coppertone::RecordParsingError; end
18
26
 
19
- class MissingSpfRecordError < Coppertone::Error; end
27
+ # Occurs when an SPF record cannot be parsed because of a duplicate
28
+ # modifier.
29
+ class DuplicateModifierError < Coppertone::RecordParsingError; end
30
+
31
+ # Occurs when more than one potential SPF record is found for a
32
+ # domain.
20
33
  class AmbiguousSpfRecordError < Coppertone::PermerrorError; end
21
34
 
35
+ # Occurs when the SPF record referenced by an include mechanism
36
+ # yields a 'none' result. This results in a permerror.
22
37
  class NoneIncludeResultError < Coppertone::PermerrorError; end
38
+
39
+ # Occurs when an SPF record cannot be found for the domain
40
+ # referenced in a redirect macro.
23
41
  class InvalidRedirectError < Coppertone::PermerrorError; end
24
42
 
43
+ # Errors generated when certain spec-defined limits are exceeded.
25
44
  class LimitExceededError < Coppertone::PermerrorError; end
26
45
  class TermLimitExceededError < PermerrorError; end
27
46
  class VoidLimitExceededError < PermerrorError; end
@@ -7,7 +7,7 @@ module Coppertone
7
7
  # of IPs in mechanism evaluation and macro string evaluation.
8
8
  #
9
9
  # Note: This class should only be used with a single IP address, and
10
- # will fail if passed an address with a prefix
10
+ # will fail if passed an address with a non-trivial network prefix
11
11
  class IPAddressWrapper
12
12
  attr_reader :string_representation, :ip
13
13
  def initialize(s)
@@ -16,12 +16,19 @@ module Coppertone
16
16
  @string_representation = s
17
17
  end
18
18
 
19
+ # Hack for JRuby - remove when JRuby moves to 2.0.x
20
+ if RUBY_VERSION < '2.0'
21
+ IP_PARSE_ERROR = ArgumentError
22
+ else
23
+ IP_PARSE_ERROR = IPAddr::InvalidAddressError
24
+ end
25
+
19
26
  def self.parse(s)
20
27
  return nil unless s
21
28
  return nil if s.index('/')
22
29
  ip_addr = IPAddr.new(s)
23
30
  normalize_ip(ip_addr)
24
- rescue IPAddr::InvalidAddressError
31
+ rescue IP_PARSE_ERROR
25
32
  nil
26
33
  end
27
34
 
@@ -48,10 +55,6 @@ module Coppertone
48
55
  original_ipv4? ? 'in-addr' : 'ip6'
49
56
  end
50
57
 
51
- def p
52
- fail NotImplementedError
53
- end
54
-
55
58
  def ip_v4
56
59
  original_ipv4? ? @ip : nil
57
60
  end
@@ -5,13 +5,13 @@ require 'uri'
5
5
 
6
6
  module Coppertone
7
7
  # A context used to evaluate MacroStrings. Responds to all of the
8
- # macro letter directives.
8
+ # macro letter directives except 'p'.
9
9
  class MacroContext
10
10
  attr_reader :domain, :ip_address_wrapper, :sender_identity, :helo_domain
11
11
 
12
12
  delegate :s, :l, :o, to: :sender_identity
13
13
  alias_method :d, :domain
14
- delegate :i, :p, :v, :c, to: :ip_address_wrapper
14
+ delegate :i, :v, :c, to: :ip_address_wrapper
15
15
  delegate :ip_v4, :ip_v6, :original_ipv4?, :original_ipv6?,
16
16
  to: :ip_address_wrapper
17
17
  alias_method :h, :helo_domain
@@ -28,25 +28,19 @@ module Coppertone
28
28
 
29
29
  UNKNOWN_HOSTNAME = 'unknown'
30
30
  def r
31
- if Coppertone::Utils::DomainUtils.valid?(raw_hostname)
32
- raw_hostname
33
- else
34
- UNKNOWN_HOSTNAME
35
- end
31
+ valid = Coppertone::Utils::DomainUtils.valid?(raw_hostname)
32
+ valid ? raw_hostname : UNKNOWN_HOSTNAME
36
33
  end
37
34
 
38
35
  def t
39
36
  Time.now.to_i
40
37
  end
41
38
 
42
- %w(s l o d i p v h c r t).each do |m|
39
+ RESERVED_REGEXP = Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
40
+ %w(s l o d i v h c r t).each do |m|
43
41
  define_method(m.upcase) do
44
42
  unencoded = send(m)
45
- if unencoded
46
- ::URI.escape(unencoded, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
47
- else
48
- nil
49
- end
43
+ unencoded ? ::URI.escape(unencoded, RESERVED_REGEXP) : nil
50
44
  end
51
45
  end
52
46
 
@@ -39,9 +39,10 @@ module Coppertone
39
39
  end
40
40
 
41
41
  def expand_ptr(context, request)
42
+ context.send(@macro_letter) if context.respond_to?(@macro_letter)
42
43
  ptr =
43
44
  Coppertone::Utils::ValidatedDomainFinder
44
- .new(context, request).find(context.d)
45
+ .new(context, request, false).find(context.d)
45
46
  return 'unknown' unless ptr
46
47
  @macro_letter == 'P' ? ::Addressable::URI.encode_component(ptr) : ptr
47
48
  end
@@ -58,7 +59,7 @@ module Coppertone
58
59
  end
59
60
 
60
61
  def to_s
61
- @body
62
+ "%{#{@body}}"
62
63
  end
63
64
 
64
65
  def ==(other)
@@ -11,16 +11,23 @@ module Coppertone
11
11
  attr_reader :macro_text
12
12
  def initialize(macro_text)
13
13
  @macro_text = macro_text
14
- parse_macros
14
+ macros
15
15
  end
16
16
 
17
- def parse_macros
18
- # Build an array of expandable macros
19
- @macros = MacroParser.new(macro_text).macros
17
+ def macros
18
+ @macros ||= MacroParser.new(macro_text).macros
20
19
  end
21
20
 
22
21
  def expand(context, request = nil)
23
- @macros.map { |m| m.expand(context, request) }.join('')
22
+ macros.map { |m| m.expand(context, request) }.join('')
23
+ end
24
+
25
+ def to_s
26
+ macros.map(&:to_s).join('')
27
+ end
28
+
29
+ def context_dependent?
30
+ macros.any? { |m| m.is_a?(Coppertone::MacroString::MacroExpand) }
24
31
  end
25
32
 
26
33
  def ==(other)
@@ -10,7 +10,11 @@ module Coppertone
10
10
  .new(macro_context, request_context)
11
11
  .check(target_name, ip_v4_cidr_length, ip_v6_cidr_length)
12
12
  end
13
+
14
+ def self.label
15
+ 'a'
16
+ end
13
17
  end
14
- register('a', Coppertone::Mechanism::A)
18
+ register(Coppertone::Mechanism::A)
15
19
  end
16
20
  end
@@ -3,9 +3,6 @@ module Coppertone
3
3
  # Implements the All mechanism. To reduce unnecessary object creation, this
4
4
  # class is a singleton since all All mechanisms behave identically.
5
5
  class All < Mechanism
6
- SINGLETON = new
7
- private_class_method :new
8
-
9
6
  def self.create(attributes)
10
7
  fail InvalidMechanismError unless attributes.blank?
11
8
  SINGLETON
@@ -15,10 +12,20 @@ module Coppertone
15
12
  SINGLETON
16
13
  end
17
14
 
15
+ def self.label
16
+ 'all'
17
+ end
18
+
19
+ def initialize
20
+ super('')
21
+ end
22
+ SINGLETON = new
23
+ private_class_method :new
24
+
18
25
  def match?(_macro_context, _request_context)
19
26
  true
20
27
  end
21
28
  end
22
- register('all', Coppertone::Mechanism::All)
29
+ register(Coppertone::Mechanism::All)
23
30
  end
24
31
  end
@@ -7,7 +7,7 @@ module Coppertone
7
7
  if length_as_i < 0 || length_as_i > max_val
8
8
  fail Coppertone::InvalidMechanismError
9
9
  end
10
- length_as_i.to_s
10
+ length_as_i
11
11
  end
12
12
  end
13
13
  end
@@ -13,6 +13,15 @@ module Coppertone
13
13
  return nil if raw_domain_spec.blank?
14
14
  raw_domain_spec[1..-1]
15
15
  end
16
+
17
+ def self.dns_lookup_term?
18
+ true
19
+ end
20
+
21
+ def context_dependent?
22
+ return true unless domain_spec
23
+ domain_spec.context_dependent?
24
+ end
16
25
  end
17
26
  end
18
27
  end
@@ -8,6 +8,7 @@ module Coppertone
8
8
  end
9
9
 
10
10
  def initialize(attributes)
11
+ super(attributes)
11
12
  return if attributes.blank?
12
13
  raw_domain_spec = trim_domain_spec(attributes)
13
14
  @domain_spec = Coppertone::DomainSpec.new(raw_domain_spec)
@@ -6,6 +6,7 @@ module Coppertone
6
6
  end
7
7
 
8
8
  def initialize(attributes)
9
+ super(attributes)
9
10
  raw_domain_spec = trim_domain_spec(attributes)
10
11
  fail InvalidMechanismError if raw_domain_spec.blank?
11
12
  @domain_spec = Coppertone::DomainSpec.new(raw_domain_spec)
@@ -10,6 +10,7 @@ module Coppertone
10
10
  end
11
11
 
12
12
  def initialize(attributes)
13
+ super(attributes)
13
14
  return if attributes.blank?
14
15
  parse_argument(attributes)
15
16
  rescue Coppertone::MacroStringParsingError
@@ -8,7 +8,11 @@ module Coppertone
8
8
  records = request_context.dns_client.fetch_a_records(target_name)
9
9
  records.any?
10
10
  end
11
+
12
+ def self.label
13
+ 'exists'
14
+ end
11
15
  end
12
- register('exists', Coppertone::Mechanism::Exists)
16
+ register(Coppertone::Mechanism::Exists)
13
17
  end
14
18
  end
@@ -7,12 +7,24 @@ module Coppertone
7
7
  # Implements the include mechanism.
8
8
  class Include < DomainSpecRequired
9
9
  def match_target_name(macro_context, request_context, target_name)
10
- context_for_include = macro_context.with_domain(target_name)
11
- record =
12
- RecordFinder.new(request_context.dns_client, target_name).record
13
- IncludeMatcher.new(record).match?(context_for_include, request_context)
10
+ record = included_record(request_context, target_name)
11
+ IncludeMatcher.new(record)
12
+ .match?(context_for_include(macro_context, target_name),
13
+ request_context)
14
+ end
15
+
16
+ def context_for_include(macro_context, target_name)
17
+ macro_context.with_domain(target_name)
18
+ end
19
+
20
+ def included_record(request_context, target_name)
21
+ RecordFinder.new(request_context.dns_client, target_name).record
22
+ end
23
+
24
+ def self.label
25
+ 'include'
14
26
  end
15
27
  end
16
- register('include', Coppertone::Mechanism::Include)
28
+ register(Coppertone::Mechanism::Include)
17
29
  end
18
30
  end
@@ -7,7 +7,11 @@ module Coppertone
7
7
  def ip_for_match(macro_context)
8
8
  macro_context.ip_v4
9
9
  end
10
+
11
+ def self.label
12
+ 'ip4'
13
+ end
10
14
  end
11
- register('ip4', Coppertone::Mechanism::IP4)
15
+ register(Coppertone::Mechanism::IP4)
12
16
  end
13
17
  end
@@ -7,7 +7,11 @@ module Coppertone
7
7
  def ip_for_match(macro_context)
8
8
  macro_context.ip_v6
9
9
  end
10
+
11
+ def self.label
12
+ 'ip6'
13
+ end
10
14
  end
11
- register('ip6', Coppertone::Mechanism::IP6)
15
+ register(Coppertone::Mechanism::IP6)
12
16
  end
13
17
  end
@@ -8,6 +8,7 @@ module Coppertone
8
8
  end
9
9
 
10
10
  def initialize(attributes)
11
+ super(attributes)
11
12
  unless attributes.blank?
12
13
  attributes = attributes[1..-1] if attributes[0] == ':'
13
14
  @ip_network = parse_ip_network(attributes)
@@ -21,6 +22,13 @@ module Coppertone
21
22
  fail Coppertone::InvalidMechanismError
22
23
  end
23
24
 
25
+ # Hack for JRuby - remove when JRuby moves to 2.0.x
26
+ if RUBY_VERSION < '2.0'
27
+ IP_PARSE_ERROR = ArgumentError
28
+ else
29
+ IP_PARSE_ERROR = IPAddr::Error
30
+ end
31
+
24
32
  def parse_ip_network(ip_as_s)
25
33
  validate_no_leading_zeroes_in_cidr(ip_as_s)
26
34
  addr, cidr_length, dual = ip_as_s.split('/')
@@ -28,7 +36,7 @@ module Coppertone
28
36
  network = IPAddr.new(addr)
29
37
  network = network.mask(cidr_length.to_i) unless cidr_length.blank?
30
38
  network
31
- rescue IPAddr::Error
39
+ rescue IP_PARSE_ERROR
32
40
  nil
33
41
  end
34
42
 
@@ -34,7 +34,11 @@ module Coppertone
34
34
  return unless limit && count > limit
35
35
  fail Coppertone::MXLimitExceededError
36
36
  end
37
+
38
+ def self.label
39
+ 'mx'
40
+ end
37
41
  end
38
- register('mx', Coppertone::Mechanism::MX)
42
+ register(Coppertone::Mechanism::MX)
39
43
  end
40
44
  end
@@ -11,7 +11,11 @@ module Coppertone
11
11
  .find(target_name)
12
12
  !matching_name.nil?
13
13
  end
14
+
15
+ def self.label
16
+ 'ptr'
17
+ end
14
18
  end
15
- register('ptr', Coppertone::Mechanism::Ptr)
19
+ register(Coppertone::Mechanism::Ptr)
16
20
  end
17
21
  end
@@ -16,8 +16,31 @@ module Coppertone
16
16
  class_builder.build(type, attributes)
17
17
  end
18
18
 
19
- def self.register(type, klass)
20
- class_builder.register(type, klass)
19
+ def self.register(klass)
20
+ fail ArgumentError unless klass < self
21
+ class_builder.register(klass.label, klass)
22
+ end
23
+
24
+ def self.dns_lookup_term?
25
+ false
26
+ end
27
+
28
+ attr_reader :arguments
29
+ def initialize(arguments)
30
+ @arguments = arguments
31
+ end
32
+
33
+ def dns_lookup_term?
34
+ self.class.dns_lookup_term?
35
+ end
36
+
37
+ def to_s
38
+ mech_label = self.class.label
39
+ arguments.blank? ? mech_label : "#{mech_label}#{arguments}"
40
+ end
41
+
42
+ def context_dependent?
43
+ false
21
44
  end
22
45
  end
23
46
  end
@@ -3,6 +3,7 @@ module Coppertone
3
3
  class Base < Modifier
4
4
  attr_reader :domain_spec
5
5
  def initialize(attributes)
6
+ super(attributes)
6
7
  fail InvalidModifierError if attributes.blank?
7
8
  @domain_spec = Coppertone::DomainSpec.new(attributes)
8
9
  rescue Coppertone::MacroStringParsingError
@@ -17,7 +17,7 @@ module Coppertone
17
17
  expanded = macro_string.expand(macro_context, request_context)
18
18
  return nil unless ASCII_REGEXP.match(expanded)
19
19
  expanded
20
- rescue Coppertone::Error
20
+ rescue DNSAdapter::Error
21
21
  nil
22
22
  end
23
23
 
@@ -28,7 +28,11 @@ module Coppertone
28
28
  return nil if records.size > 1
29
29
  MacroString.new(records.first[:text])
30
30
  end
31
+
32
+ def self.label
33
+ 'exp'
34
+ end
31
35
  end
32
- register('exp', Coppertone::Modifier::Exp)
36
+ register(Coppertone::Modifier::Exp)
33
37
  end
34
38
  end
@@ -11,7 +11,11 @@ module Coppertone
11
11
  request_context.register_dns_lookup_term
12
12
  target_name_from_domain_spec(macro_context, request_context)
13
13
  end
14
+
15
+ def self.label
16
+ 'redirect'
17
+ end
14
18
  end
15
- register('redirect', Coppertone::Modifier::Redirect)
19
+ register(Coppertone::Modifier::Redirect)
16
20
  end
17
21
  end
@@ -6,11 +6,16 @@ module Coppertone
6
6
  end
7
7
 
8
8
  def initialize(attributes)
9
+ super(attributes)
9
10
  @macro_string = Coppertone::MacroString.new(attributes)
10
11
  rescue Coppertone::MacroStringParsingError
11
12
  raise Coppertone::InvalidModifierError
12
13
  end
14
+
15
+ def self.label
16
+ 'unknown'
17
+ end
13
18
  end
14
- register('unknown', Coppertone::Modifier::Unknown)
19
+ register(Coppertone::Modifier::Unknown)
15
20
  end
16
21
  end
@@ -10,8 +10,8 @@ module Coppertone
10
10
  class_builder.build(type, attributes)
11
11
  end
12
12
 
13
- def self.register(type, klass)
14
- class_builder.register(type, klass)
13
+ def self.register(klass)
14
+ class_builder.register(klass.label, klass)
15
15
  end
16
16
 
17
17
  MODIFIER_REGEXP = /\A([a-zA-Z]+[a-zA-Z0-9\-\_\.]*)=(\S*)\z/
@@ -22,6 +22,15 @@ module Coppertone
22
22
  attributes = matches[2]
23
23
  build(type, attributes) || build('unknown', attributes)
24
24
  end
25
+
26
+ attr_reader :arguments
27
+ def initialize(arguments)
28
+ @arguments = arguments
29
+ end
30
+
31
+ def to_s
32
+ "#{self.class.label}=#{arguments}"
33
+ end
25
34
  end
26
35
  end
27
36
 
@@ -0,0 +1,23 @@
1
+ module Coppertone
2
+ # A context used to evaluate records, directives, and modifiers that do not have
3
+ # contextual dependence.
4
+ class NullMacroContext
5
+
6
+ RESERVED_REGEXP = Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
7
+ %w(s l o d i p v h c r t).each do |m|
8
+ define_method(m.upcase) do
9
+ fail ArgumentError
10
+ end
11
+
12
+ define_method(m) do
13
+ fail ArgumentError
14
+ end
15
+ end
16
+
17
+ def with_domain(new_domain)
18
+ self
19
+ end
20
+
21
+ NULL_CONTEXT = new
22
+ end
23
+ end