coppertone 0.0.1 → 0.0.2

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