spf 0.0.49 → 0.0.53

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7d7611765c97ea142a4b2809fc4f75d61ea0ee3d
4
- data.tar.gz: 7b714243e6934102d5e07486227f0db0aeb3c210
2
+ SHA256:
3
+ metadata.gz: 9b5b96521a48a4f0b0a78397d6e21ff99d03b9d144df182bfd68b0369046d73e
4
+ data.tar.gz: 17025632b91737a607a5035b9dfd2405bb080eeaee3e8091cd5e71dc4729cd4d
5
5
  SHA512:
6
- metadata.gz: ca52c3eaec492ee51afde4ad20fac2d78bd48402005f320e700774afe8fb36298ead25da53eb34d04c512d7e5af0661e802b462d1f0c5589606a2eaba5d85117
7
- data.tar.gz: bc10bbbf14fa2bb9a26ac55cde3793a914f5249ff5ee7909347c2dfb9225ce28284d22c064c2cbe2edd54e1b1c2257d665b56a9728b942db6a782b53c8a64188
6
+ metadata.gz: aea5c665821e18e2724d0d0e92ca02a89347eabc53226135944b9de65b7b20d25f64401128606f49367a718e637b2d425fa6bb2fdb0999b3cbecae72c5698852
7
+ data.tar.gz: f4f74a840d9fbe744d5b2fbf52abb5011f5c75672b648e6d0512450d7261997ee4006a83f707ec50300639972a235b0d261e11ac976bddf5a2eea13db8944150
data/Gemfile CHANGED
@@ -9,8 +9,8 @@ gem "ruby-ip", "~> 0.9.1"
9
9
  # Include everything needed to run rake, tests, features, etc.
10
10
  group :development do
11
11
  gem "rspec", "~> 2.9"
12
- gem "rdoc", "~> 3"
12
+ gem "rdoc", "~> 4.3"
13
13
  gem "bundler", "~> 1.2"
14
- gem "jeweler", "~> 1.8"
14
+ gem "jeweler", "~> 2.3", ">= 2.3.9"
15
15
  gem "simplecov", :require => false, :group => :test
16
16
  end
@@ -1,47 +1,55 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- addressable (2.3.7)
5
- builder (3.2.2)
4
+ addressable (2.4.0)
5
+ builder (3.2.4)
6
+ descendants_tracker (0.0.4)
7
+ thread_safe (~> 0.3, >= 0.3.1)
6
8
  diff-lcs (1.2.5)
7
9
  docile (1.1.5)
8
- faraday (0.8.9)
9
- multipart-post (~> 1.2.0)
10
- git (1.2.9.1)
11
- github_api (0.10.1)
12
- addressable
13
- faraday (~> 0.8.1)
14
- hashie (>= 1.2)
15
- multi_json (~> 1.4)
16
- nokogiri (~> 1.5.2)
17
- oauth2
18
- hashie (3.4.0)
19
- highline (1.7.1)
20
- jeweler (1.8.8)
10
+ faraday (0.9.2)
11
+ multipart-post (>= 1.2, < 3)
12
+ git (1.7.0)
13
+ rchardet (~> 1.8)
14
+ github_api (0.16.0)
15
+ addressable (~> 2.4.0)
16
+ descendants_tracker (~> 0.0.4)
17
+ faraday (~> 0.8, < 0.10)
18
+ hashie (>= 3.4)
19
+ mime-types (>= 1.16, < 3.0)
20
+ oauth2 (~> 1.0)
21
+ hashie (4.1.0)
22
+ highline (2.0.3)
23
+ jeweler (2.3.9)
21
24
  builder
22
- bundler (~> 1.0)
25
+ bundler
23
26
  git (>= 1.2.5)
24
- github_api (= 0.10.1)
27
+ github_api (~> 0.16.0)
25
28
  highline (>= 1.6.15)
26
- nokogiri (= 1.5.10)
29
+ nokogiri (>= 1.5.10)
30
+ psych
27
31
  rake
28
32
  rdoc
29
- json (1.8.2)
30
- jwt (1.3.0)
31
- multi_json (1.10.1)
32
- multi_xml (0.5.5)
33
- multipart-post (1.2.0)
34
- nokogiri (1.5.10)
35
- oauth2 (1.0.0)
36
- faraday (>= 0.8, < 0.10)
37
- jwt (~> 1.0)
33
+ semver2
34
+ jwt (2.2.2)
35
+ mime-types (2.99.3)
36
+ mini_portile2 (2.4.0)
37
+ multi_json (1.15.0)
38
+ multi_xml (0.6.0)
39
+ multipart-post (2.1.1)
40
+ nokogiri (1.10.10)
41
+ mini_portile2 (~> 2.4.0)
42
+ oauth2 (1.4.4)
43
+ faraday (>= 0.8, < 2.0)
44
+ jwt (>= 1.0, < 3.0)
38
45
  multi_json (~> 1.3)
39
46
  multi_xml (~> 0.5)
40
- rack (~> 1.2)
41
- rack (1.6.0)
42
- rake (10.4.2)
43
- rdoc (3.12.2)
44
- json (~> 1.4)
47
+ rack (>= 1.2, < 3)
48
+ psych (3.2.0)
49
+ rack (2.2.3)
50
+ rake (13.0.1)
51
+ rchardet (1.8.0)
52
+ rdoc (4.3.0)
45
53
  rspec (2.99.0)
46
54
  rspec-core (~> 2.99.0)
47
55
  rspec-expectations (~> 2.99.0)
@@ -51,19 +59,24 @@ GEM
51
59
  diff-lcs (>= 1.1.3, < 2.0)
52
60
  rspec-mocks (2.99.3)
53
61
  ruby-ip (0.9.3)
62
+ semver2 (3.4.2)
54
63
  simplecov (0.9.2)
55
64
  docile (~> 1.1.0)
56
65
  multi_json (~> 1.0)
57
66
  simplecov-html (~> 0.9.0)
58
67
  simplecov-html (0.9.0)
68
+ thread_safe (0.3.6)
59
69
 
60
70
  PLATFORMS
61
71
  ruby
62
72
 
63
73
  DEPENDENCIES
64
74
  bundler (~> 1.2)
65
- jeweler (~> 1.8)
66
- rdoc (~> 3)
75
+ jeweler (~> 2.3, >= 2.3.9)
76
+ rdoc (~> 4.3)
67
77
  rspec (~> 2.9)
68
78
  ruby-ip (~> 0.9.1)
69
79
  simplecov
80
+
81
+ BUNDLED WITH
82
+ 1.17.3
@@ -50,15 +50,15 @@ module SPF
50
50
  class InvalidModError < SyntaxError; end # Invalid modifier
51
51
  class InvalidTermError < SyntaxError; end # Invalid term
52
52
  class JunkInTermError < SyntaxError; end # Junk encountered in term
53
- class DuplicateGlobalMod < InvalidModError; end # Duplicate global modifier
53
+ class DuplicateGlobalModError < InvalidModError; end # Duplicate global modifier
54
54
  class InvalidMechError < InvalidTermError; end # Invalid mechanism
55
55
  class InvalidMechQualifierError < InvalidMechError; end # Invalid mechanism qualifier
56
56
  class InvalidMechCIDRError < InvalidMechError; end # Invalid CIDR netblock in mech
57
57
  class TermDomainSpecExpectedError < SyntaxError; end # Missing required <domain-spec> in term
58
58
  class TermIPv4AddressExpectedError < SyntaxError; end # Missing required <ip4-network> in term
59
- class TermIPv4PrefixLengthExpected < SyntaxError; end # Missing required <ip4-cidr-length> in term
60
- class TermIPv6AddressExpected < SyntaxError; end # Missing required <ip6-network> in term
61
- class TermIPv6PrefixLengthExpected < SyntaxError; end # Missing required <ip6-cidr-length> in term
59
+ class TermIPv4PrefixLengthExpectedError < SyntaxError; end # Missing required <ip4-cidr-length> in term
60
+ class TermIPv6AddressExpectedError < SyntaxError; end # Missing required <ip6-network> in term
61
+ class TermIPv6PrefixLengthExpectedError < SyntaxError; end # Missing required <ip6-cidr-length> in term
62
62
  class InvalidMacroStringError < SyntaxError; end # Invalid macro string
63
63
  class InvalidMacroError < InvalidMacroStringError
64
64
  end # Invalid macro
@@ -275,7 +275,13 @@ class SPF::Server
275
275
  versions.each do |version|
276
276
  klass = RECORD_CLASSES_BY_VERSION[version]
277
277
  begin
278
- record = klass.new_from_string(text, {:raise_exceptions => @raise_exceptions})
278
+ options = {:raise_exceptions => @raise_exceptions}
279
+ # A MacroString object for domain indicates this is a nested record.
280
+ # Storing the domain.text maintains an association to the include domain.
281
+ if domain.class == SPF::MacroString
282
+ options[:record_domain] = domain.text
283
+ end
284
+ record = klass.new_from_string(text, options)
279
285
  rescue SPF::InvalidRecordVersionError => error
280
286
  if text =~ /#{LOOSE_SPF_MATCH_PATTERN}/
281
287
  possible_matches << text
@@ -1,5 +1,8 @@
1
1
  # encoding: ASCII-8BIT
2
2
  require 'spf/util'
3
+ require 'spf/error'
4
+ require 'uri'
5
+
3
6
 
4
7
  module SPF
5
8
  class MacroString
@@ -22,8 +25,8 @@ module SPF
22
25
  or raise ArgumentError, "Missing required 'text' option"
23
26
  @server = options[:server]
24
27
  @request = options[:request]
28
+ @is_explanation = options[:is_explanation]
25
29
  @expanded = nil
26
- self.expand
27
30
  end
28
31
 
29
32
  attr_reader :text, :server, :request
@@ -43,9 +46,124 @@ module SPF
43
46
  return (@expanded = @text) unless @text =~ /%/
44
47
  # Short-circuit expansion if text has no '%' characters.
45
48
 
49
+ server, request = context ? context : [@server, @request]
50
+
51
+ valid_context(true, server, request)
52
+
46
53
  expanded = ''
47
- # TODO
48
- return (@expanded = @text)
54
+
55
+ text = @text
56
+
57
+ while m = text.match(/ (.*?) %(.) /x) do
58
+ expanded += m[1]
59
+ key = m[2]
60
+
61
+ if (key == '{')
62
+ if m2 = m.post_match.match(/ (\w|_\p{Alpha}+) ([0-9]+)? (r)? ([.\-+,\/_=])? } /x)
63
+ char, rh_parts, reverse, delimiter = m2.captures
64
+
65
+ # Upper-case macro chars trigger URL-escaping AKA percent-encoding
66
+ # (RFC 4408, 8.1/26):
67
+ do_percent_encode = char =~ /\p{Upper}/
68
+ char.downcase!
69
+
70
+ if char == 's' # RFC 4408, 8.1/19
71
+ value = request.identity
72
+ elsif char == 'l' # RFC 4408, 8.1/19
73
+ value = request.localpart
74
+ elsif char == 'o' # RFC 4408, 8.1/19
75
+ value = request.domain
76
+ elsif char == 'd' # RFC 4408, 8.1/6/4
77
+ value = request.authority_domain
78
+ elsif char == 'i' # RFC 4408, 8.1/20, 8.1/21
79
+ ip_address = request.ip_address
80
+ ip_address = SPF::Util.ipv6_address_to_ipv4(ip_address) if SPF::Util.ipv6_address_is_ipv4_mapped(ip_address)
81
+ if IP::V4 === ip_address
82
+ value = ip_address.to_addr
83
+ elsif IP::V6 === ip_address
84
+ value = ip_address.to_hex.upcase.split('').join('.')
85
+ else
86
+ server.throw_result(:permerror, request, "Unexpected IP address version in request")
87
+ end
88
+ elsif char == 'p' # RFC 4408, 8.1/22
89
+ # According to RFC 7208 the "p" macro letter should not be used (or even published).
90
+ # Here it is left unexpanded and transformers and delimiters are not applied.
91
+ value = '%{' + m2.to_s
92
+ rh_parts = nil
93
+ reverse = nil
94
+ elsif char == 'v' # RFC 4408, 8.1/6/7
95
+ if IP::V4 === request.ip_address
96
+ value = 'in-addr'
97
+ elsif IP::V6 === request.ip_address
98
+ value = 'ip6'
99
+ else
100
+ # Unexpected IP address version.
101
+ server.throw_result(:permerror, request, "Unexpected IP address version in request")
102
+ end
103
+ elsif char == 'h' # RFC 4408, 8.1/6/8
104
+ value = request.helo_identity || 'unknown'
105
+ elsif char == 'c' # RFC 4408, 8.1/20, 8.1/21
106
+ raise SPF::InvalidMacroStringError.new("Illegal 'c' macro in non-explanation macro string '#{@text}'") unless @is_explanation
107
+ ip_address = request.ip_address
108
+ value = SPF::Util::ip_address_to_string(ip_address)
109
+ elsif char == 'r' # RFC 4408, 8.1/23
110
+ value = server.hostname || 'unknown'
111
+ elsif char == 't'
112
+ raise SPF::InvalidMacroStringError.new("Illegal 't' macro in non-explanation macro string '#{@text}'") unless @is_explanation
113
+ value = Time.now.to_i.to_s
114
+ elsif char == '_scope'
115
+ # Scope pseudo macro for internal use only!
116
+ value = request.scope.to_s
117
+ else
118
+ # Unknown macro character.
119
+ raise SPF::InvalidMacroStringError.new("Invalid macro character #{char} in macro string '#{@text}'")
120
+ end
121
+
122
+ if rh_parts || reverse
123
+ delimiter ||= self.class.default_split_delimiters
124
+ list = value.split(delimiter)
125
+ list.reverse! if reverse
126
+ # Extract desired parts:
127
+ if rh_parts && rh_parts.to_i > 0
128
+ list = list.last(rh_parts.to_i)
129
+ end
130
+ if rh_parts && rh_parts.to_i == 0
131
+ raise SPF::InvalidMacroStringError.new("Illegal selection of 0 (zero) right-hand parts in macro string '#{@text}'")
132
+ end
133
+ value = list.join(self.class.default_join_delimiter)
134
+ end
135
+
136
+ if do_percent_encode
137
+ unsafe = Regexp.new('^' + self.class.uri_unreserved_chars)
138
+ value = URI.escape(value, unsafe)
139
+ end
140
+
141
+ expanded += value
142
+
143
+ text = m2.post_match
144
+ else
145
+ # Invalid macro expression.
146
+ raise SPF::InvalidMacroStringError.new("Invalid macro expression in macro string '#{@text}'")
147
+ end
148
+ elsif key == '-'
149
+ expanded += '-'
150
+ text = m.post_match
151
+ elsif key == '_'
152
+ expanded += ' '
153
+ text = m.post_match
154
+ elsif key == '%'
155
+ expanded += '%'
156
+ text = m.post_match
157
+ else
158
+ # Invalid macro expression.
159
+ pos = m.offset(2).first
160
+ raise SPF::InvalidMacroStringError.new("Invalid macro expression at pos #{pos} in macro string '#{@text}'")
161
+ end
162
+ end
163
+
164
+ expanded += text # Append remaining unmatched characters.
165
+
166
+ context ? expanded : @expanded = expanded
49
167
  end
50
168
 
51
169
  def to_s
@@ -58,16 +176,15 @@ module SPF
58
176
 
59
177
  def valid_context(required, server = self.server, request = self.request)
60
178
  if not SPF::Server === server
61
- raise MacroExpansionCtxRequired, 'SPF server object required' if required
179
+ raise SPF::MacroExpansionCtxRequiredError.new('SPF server object required') if required
62
180
  return false
63
181
  end
64
182
  if not SPF::Request === request
65
- raise MacroExpansionCtxRequired, 'SPF request object required' if required
183
+ raise SPF::MacroExpansionCtxRequiredError.new('SPF request object required') if required
66
184
  return false
67
185
  end
68
186
  return true
69
187
  end
70
-
71
188
  end
72
189
  end
73
190
 
@@ -86,7 +86,7 @@ class SPF::Term
86
86
  ::
87
87
  "
88
88
 
89
- attr_reader :errors, :ip_netblocks, :ip_address, :ip_network, :ipv4_prefix_length, :ipv6_prefix_length, :domain_spec, :raw_params
89
+ attr_reader :errors, :ip_netblocks, :ip_address, :ip_network, :ipv4_prefix_length, :ipv6_prefix_length, :domain_spec, :raw_params, :record_domain
90
90
 
91
91
  def initialize(options = {})
92
92
  @ip_address = nil
@@ -97,6 +97,7 @@ class SPF::Term
97
97
  @errors = []
98
98
  @ip_netblocks = []
99
99
  @text = options[:text]
100
+ @record_domain = options[:record_domain]
100
101
  @raise_exceptions = options.has_key?(:raise_exceptions) ? options[:raise_exceptions] : true
101
102
  end
102
103
 
@@ -117,6 +118,8 @@ class SPF::Term
117
118
  domain_spec = $1
118
119
  domain_spec.sub!(/^(.*?)\.?$/, $1)
119
120
  @domain_spec = SPF::MacroString.new({:text => domain_spec})
121
+ elsif record_domain
122
+ @domain_spec = SPF::MacroString.new({:text => record_domain})
120
123
  elsif required
121
124
  error(SPF::TermDomainSpecExpectedError.new(
122
125
  "Missing required domain-spec in '#{@text}'"))
@@ -139,13 +142,13 @@ class SPF::Term
139
142
  if @parse_text.sub!(/^\/(\d+)/, '')
140
143
  bits = $1.to_i
141
144
  unless bits and bits >= 0 and bits <= 32 and $1 !~ /^0./
142
- error(SPF::TermIPv4PrefixLengthExpected.new(
145
+ error(SPF::TermIPv4PrefixLengthExpectedError.new(
143
146
  "Invalid IPv4 prefix length encountered in '#{@text}'"))
144
147
  return
145
148
  end
146
149
  @ipv4_prefix_length = bits
147
150
  elsif required
148
- error(SPF::TermIPv4PrefixLengthExpected.new(
151
+ error(SPF::TermIPv4PrefixLengthExpectedError.new(
149
152
  "Missing required IPv4 prefix length in '#{@text}"))
150
153
  return
151
154
  else
@@ -168,7 +171,7 @@ class SPF::Term
168
171
  if @parse_text.sub!(/(#{IPV6_ADDRESS_PATTERN})(?=\/|$)/x, '')
169
172
  @ip_address = $1
170
173
  elsif required
171
- error(SPF::TermIPv6AddressExpected.new(
174
+ error(SPF::TermIPv6AddressExpectedError.new(
172
175
  "Missing or invalid required IPv6 address in '#{@text}'"))
173
176
  end
174
177
  @ip_address = @parse_text.dup unless @ip_address
@@ -184,7 +187,7 @@ class SPF::Term
184
187
  end
185
188
  @ipv6_prefix_length = bits
186
189
  elsif required
187
- error(SPF::TermIPvPrefixLengthExpected.new(
190
+ error(SPF::TermIPv6PrefixLengthExpectedError.new(
188
191
  "Missing required IPv6 prefix length in '#{@text}'"))
189
192
  return
190
193
  else
@@ -214,7 +217,7 @@ class SPF::Term
214
217
 
215
218
  def domain(server, request)
216
219
  if self.instance_variable_defined?(:@domain_spec) and @domain_spec
217
- return @domain_spec
220
+ return SPF::MacroString.new({:server => server, :request => request, :text => @domain_spec.text})
218
221
  end
219
222
  return request.authority_domain
220
223
  end
@@ -446,13 +449,13 @@ class SPF::Mech < SPF::Term
446
449
  server.count_dns_interactive_term(request)
447
450
 
448
451
  domain = self.domain(server, request)
449
- packet = server.dns_lookup(domain, 'A')
450
- rrs = (packet.exchange or server.count_void_dns_lookup(request))
451
- rrs.each do |rr|
452
- return true if rr.type == 'A'
452
+ begin
453
+ rrs = server.dns_lookup(domain, 'A')
454
+ return true if rrs.any?
455
+ rescue SPF::DNSNXDomainError => e
456
+ server.count_void_dns_lookup(request)
457
+ return false
453
458
  end
454
-
455
- return false
456
459
  end
457
460
 
458
461
  end
@@ -844,6 +847,7 @@ class SPF::Record
844
847
  @global_mods ||= {}
845
848
  @errors = []
846
849
  @ip_netblocks = []
850
+ @record_domain = options[:record_domain]
847
851
  @raise_exceptions = options.has_key?(:raise_exceptions) ? options[:raise_exceptions] : true
848
852
  end
849
853
 
@@ -914,7 +918,11 @@ class SPF::Record
914
918
  error(exception)
915
919
  mech_class = SPF::Mech
916
920
  end
917
- term = mech = mech_class.new_from_string(mech_text, {:raise_exceptions => @raise_exceptions})
921
+ options = {:raise_exceptions => @raise_exceptions}
922
+ if instance_variable_defined?("@record_domain")
923
+ options[:record_domain] = @record_domain
924
+ end
925
+ term = mech = mech_class.new_from_string(mech_text, options)
918
926
  term.errors << exception if exception
919
927
  @ip_netblocks << mech.ip_netblocks if mech.ip_netblocks
920
928
  @terms << mech
@@ -941,7 +949,7 @@ class SPF::Record
941
949
  if SPF::GlobalMod === mod
942
950
  # Global modifier.
943
951
  if @global_mods[mod_name]
944
- raise SPF::DuplicateGlobalMod.new("Duplicate global modifier '#{mod_name}' encountered")
952
+ raise SPF::DuplicateGlobalModError.new("Duplicate global modifier '#{mod_name}' encountered")
945
953
  end
946
954
  @global_mods[mod_name] = mod
947
955
  elsif SPF::PositionalMod === mod
@@ -1,6 +1,6 @@
1
1
  # encoding: ASCII-8BIT
2
2
  module SPF
3
- VERSION = '0.0.49'
3
+ VERSION = '0.0.53'
4
4
  end
5
5
 
6
6
  # vim:sw=2 sts=2
@@ -6,7 +6,7 @@
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "spf"
9
- s.version = "0.0.49"
9
+ s.version = "0.0.53"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.49
4
+ version: 0.0.53
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Flury
@@ -133,8 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
133
  - !ruby/object:Gem::Version
134
134
  version: '0'
135
135
  requirements: []
136
- rubyforge_project:
137
- rubygems_version: 2.4.6
136
+ rubygems_version: 3.0.6
138
137
  signing_key:
139
138
  specification_version: 4
140
139
  summary: Implementation of the Sender Policy Framework