dmarcator 0.11.0 → 0.12.0

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
2
  SHA256:
3
- metadata.gz: 689cd037f395f7803f4157fd148305f1d9d6c3dbb19fd42b61311b8e37d57124
4
- data.tar.gz: 80518a23a140cb7102cb4d20a5fa896852ca8b104a3047939bcf4ba018e89699
3
+ metadata.gz: b6c289ca56b406c5f591f414ada16407028e732310ad9b4c784e2f231469828a
4
+ data.tar.gz: 24f260da56f98e045d262bd5fcbe3ee9d3f8be1b1bdd310e26e8b9b4b74f1021
5
5
  SHA512:
6
- metadata.gz: c3b5f59702e05d0ffeeec17c9c3e7f8c1bd078448ba029fd4cf6bc8e6cbd8107dbda2e3633c228d1706f09832023a8a64b9806d6ee843b1fe13d449b19379e0c
7
- data.tar.gz: de98c5111a0eb8023fa10807c7227d18bead12e17f2f43d084d8c5e3d27a2b1a4e86212c0c075fa2e75a97e5710e738de2baad45b833c31249d54a1abec80bb2
6
+ metadata.gz: f88871eab61d67fcccbe7ac0e6f0dfc8f97160592d97e69e58a69d9d99b069ec02e70b9126a0dc7ae114c9e50d599a0ca014ba9b02fc2514fab7db97c4bd4a08
7
+ data.tar.gz: 2d10f64bd4cd096f54ba2a6372a07c582dc3aad581fe556a3228be4378ba09bd9e3e5ba57c94a4cd98acc4b87899ded063ab9b886b73390267659754713297bb
data/ChangeLog.md CHANGED
@@ -1,3 +1,25 @@
1
+ ### 0.12.0 / 2026-10-29
2
+
3
+ Breaking changes, taken before 1.0:
4
+
5
+ * `Report#version` returns the published value as a String instead of a `BigDecimal`, and the `bigdecimal` runtime dependency is gone. A non-numeric version still yields `nil`.
6
+ * Parsed report objects (`Report::Metadata`, `Policy`, `Row`, `AuthResult`, `Reason`) are read-only. Their attributes were previously writable, which allowed a parsed report to be mutated into a state that never came from the document.
7
+ * `Report.max_report_bytes` now delegates to `DMARCator.config.max_report_bytes`. The class-level accessors remain, so existing callers keep working; `Config#max_report_bytes` defaults to 25 MiB and rejects values that are not a positive Integer or nil.
8
+
9
+ Other changes:
10
+
11
+ * `DMARCator::XmlHelper` methods are private to including classes, `Record.truncate_error_message` is private, and `DMARCator::Parser` is documented as `@api private`.
12
+ * Add an API-contract spec that pins the public surface of every public class, so additions and removals are deliberate.
13
+ * Add `SECURITY.md` and document the stability, thread-safety and deprecation policies in the README.
14
+
15
+ Migration:
16
+
17
+ | Before | After |
18
+ |---|---|
19
+ | `report.version == BigDecimal("0.1")` | `report.version == "0.1"` |
20
+ | `report.policy.pct = 50` | assign nothing; parsed values are read-only |
21
+ | `Report.max_report_bytes = n` | `DMARCator.configure { |c| c.max_report_bytes = n }` |
22
+
1
23
  ### 0.11.0 / 2026-10-29
2
24
 
3
25
  * Add `DMARCator.configure` for process-wide defaults, so a resolver can be set from code instead of only through the `DMARCATOR_DNS` environment variables. A configured resolver takes precedence over the environment, and `DMARCator.reset_configuration!` discards it.
data/README.md CHANGED
@@ -201,6 +201,7 @@ Process-wide defaults can be set from code, and take precedence over the
201
201
  ```ruby
202
202
  DMARCator.configure do |config|
203
203
  config.resolver = DMARCator::Resolver::DoH.new(url: "https://dns.example/dns-query")
204
+ config.max_report_bytes = 5 * 1024 * 1024
204
205
  end
205
206
  ```
206
207
 
@@ -236,7 +237,7 @@ Parse a DMARC aggregate/forensic report:
236
237
  report = DMARCator::Report.new(xml_string)
237
238
 
238
239
  report.version
239
- # => 0.1e0
240
+ # => "0.1"
240
241
 
241
242
  report.metadata.org_name
242
243
  # => "google.com"
@@ -287,18 +288,44 @@ resolver = DMARCator::Resolver::DoH.new(url: "https://dns.example/dns-query")
287
288
  record = DMARCator::Record.lookup("example.com", resolver: resolver)
288
289
  ```
289
290
 
291
+ ## Stability
292
+
293
+ The public API is pinned by a contract spec in the test suite: the module
294
+ methods, constants and per-class public methods it asserts are the supported
295
+ surface. Anything marked `@api private` in the documentation — the parser, the
296
+ XML helper, the internal resolver plumbing — may change in a minor release.
297
+
298
+ ### Thread safety
299
+
300
+ * `DMARCator::Record`, `DMARCator::Report` and the parsed value objects are
301
+ read-only after construction and safe to share between threads.
302
+ * The memoized parser and transform are shared, and safe for concurrent use.
303
+ * Resolvers are not necessarily safe for concurrent lookups: `Resolv::DNS` does
304
+ not serialise queries. Use one resolver per thread, or configure a factory
305
+ with `DMARCator.configure` so each lookup builds its own.
306
+ * `DMARCator.configure` mutates process-wide state. Call it during boot rather
307
+ than per request.
308
+
309
+ ### Deprecations
310
+
311
+ A method is deprecated in a minor release by documentation only — no runtime
312
+ warnings, so nothing is added to application logs. Deprecated methods are
313
+ removed in the next major release.
314
+
290
315
  ## Security
291
316
 
292
- DMARC records and XML reports are treated as untrusted input:
317
+ DMARC records and XML reports are treated as untrusted input. See
318
+ [SECURITY.md](https://github.com/bermannoah/dmarcator/blob/main/SECURITY.md)
319
+ for the reporting policy and scope.
293
320
 
294
321
  * XML reports are parsed with network access disabled (`NONET`), so external
295
322
  entities are never resolved or fetched.
296
- * Reports larger than `DMARCator::Report.max_report_bytes` (25 MiB by
323
+ * Reports larger than `DMARCator.config.max_report_bytes` (25 MiB by
297
324
  default) are rejected before parsing. Parsing builds a DOM roughly 13× the
298
325
  input size, so lower the limit to bound memory:
299
326
 
300
327
  ```ruby
301
- DMARCator::Report.max_report_bytes = 5 * 1024 * 1024
328
+ DMARCator.configure { |config| config.max_report_bytes = 5 * 1024 * 1024 }
302
329
  ```
303
330
  * A warning is emitted at load time if the bundled `resolv` gem is affected by
304
331
  CVE-2026-80212/CVE-2026-80213.
@@ -316,7 +343,6 @@ DMARC records and XML reports are treated as untrusted input:
316
343
  * [Ruby] >= 3.3.13
317
344
  * [parslet] >= 1.0.0, < 3.0.0
318
345
  * [nokogiri] >= 1.10
319
- * [bigdecimal] >= 1.0
320
346
 
321
347
  ## Testing
322
348
 
@@ -386,5 +412,4 @@ created by D Salko and released under the MIT License.
386
412
  [Ruby]: https://www.ruby-lang.org
387
413
  [parslet]: https://kschiess.github.io/parslet/
388
414
  [nokogiri]: https://nokogiri.org
389
- [bigdecimal]: https://github.com/ruby/bigdecimal
390
415
  [LICENSE.txt]: LICENSE.txt
@@ -7,6 +7,15 @@ module DMARCator
7
7
  # @since 0.11.0
8
8
  #
9
9
  class Config
10
+ # Default maximum accepted report size in bytes (25 MiB).
11
+ #
12
+ # Parsing a report builds a DOM roughly 13x the input size, so this bounds
13
+ # the memory a hostile report can cost.
14
+ #
15
+ # @since 0.12.0
16
+ #
17
+ DEFAULT_MAX_REPORT_BYTES = 25 * 1024 * 1024
18
+
10
19
  #
11
20
  # The default resolver used when a lookup is not given one.
12
21
  #
@@ -18,6 +27,36 @@ module DMARCator
18
27
  # @return [Object, #call, nil]
19
28
  #
20
29
  attr_accessor :resolver
30
+
31
+ #
32
+ # The maximum accepted report size in bytes.
33
+ #
34
+ # Defaults to {DEFAULT_MAX_REPORT_BYTES}. Lower it to bound memory for
35
+ # reports from untrusted sources.
36
+ #
37
+ # @return [Integer]
38
+ #
39
+ def max_report_bytes
40
+ @max_report_bytes || DEFAULT_MAX_REPORT_BYTES
41
+ end
42
+
43
+ #
44
+ # Sets the maximum accepted report size in bytes.
45
+ #
46
+ # @param [Integer, nil] value
47
+ # A positive number of bytes, or `nil` to restore the default.
48
+ #
49
+ # @raise [ArgumentError]
50
+ # The value is not a positive Integer or nil.
51
+ #
52
+ def max_report_bytes=(value)
53
+ unless value.nil? || (value.is_a?(Integer) && value.positive?)
54
+ raise ArgumentError,
55
+ "max_report_bytes must be a positive Integer or nil, got #{value.inspect}"
56
+ end
57
+
58
+ @max_report_bytes = value
59
+ end
21
60
  end
22
61
 
23
62
  #
@@ -10,6 +10,10 @@ module DMARCator
10
10
  #
11
11
  # DMARC parser.
12
12
  #
13
+ # An implementation detail: use {Record.parse} rather than this class.
14
+ #
15
+ # @api private
16
+ #
13
17
  # @see https://tools.ietf.org/html/rfc7489#section-6.4
14
18
  #
15
19
  class Parser < Parslet::Parser
@@ -312,6 +312,8 @@ module DMARCator
312
312
  end
313
313
  end
314
314
 
315
+ private_class_method :truncate_error_message
316
+
315
317
  #
316
318
  # Looks up and parses the DMARC record for a domain.
317
319
  #
@@ -13,32 +13,32 @@ module DMARCator
13
13
  # Attribute names exposed by this class.
14
14
  ATTRIBUTES = [:type, :domain, :result, :selector, :scope]
15
15
 
16
- # @!attribute [rw] type
16
+ # @!attribute [r] type
17
17
  # @return [String] the entry name (`dkim` or `spf`).
18
- # @!attribute [rw] domain
18
+ # @!attribute [r] domain
19
19
  # @return [String, nil] the authenticated domain.
20
- # @!attribute [rw] result
20
+ # @!attribute [r] result
21
21
  # @return [String, nil] the authentication result.
22
- # @!attribute [rw] selector
22
+ # @!attribute [r] selector
23
23
  # @return [String, nil] the DKIM selector.
24
- # @!attribute [rw] scope
24
+ # @!attribute [r] scope
25
25
  # @return [String, nil] the SPF scope.
26
- ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
26
+ ATTRIBUTES.each { |attribute| attr_reader(attribute) }
27
27
 
28
28
  def initialize(node)
29
29
  @node = node
30
- self.type = node.name
30
+ @type = node.name
31
31
 
32
32
  node.element_children.each do |element|
33
33
  case element.name
34
34
  when 'domain'
35
- self.domain ||= element.text
35
+ @domain ||= element.text
36
36
  when 'result'
37
- self.result ||= element.text
37
+ @result ||= element.text
38
38
  when 'selector'
39
- self.selector ||= element.text
39
+ @selector ||= element.text
40
40
  when 'scope'
41
- self.scope ||= element.text
41
+ @scope ||= element.text
42
42
  end
43
43
  end
44
44
  end
@@ -16,25 +16,25 @@ module DMARCator
16
16
  :begin_at, :end_at, :errors
17
17
  ]
18
18
 
19
- # @!attribute [rw] org_name
19
+ # @!attribute [r] org_name
20
20
  # @return [String, nil] the reporting organization.
21
- # @!attribute [rw] email
21
+ # @!attribute [r] email
22
22
  # @return [String, nil] the reporting organization's contact address.
23
- # @!attribute [rw] extra_contact_info
23
+ # @!attribute [r] extra_contact_info
24
24
  # @return [String, nil] additional contact information.
25
- # @!attribute [rw] report_id
25
+ # @!attribute [r] report_id
26
26
  # @return [String, nil] the unique report identifier.
27
- # @!attribute [rw] begin_at
27
+ # @!attribute [r] begin_at
28
28
  # @return [Time, nil] the start of the reporting interval.
29
- # @!attribute [rw] end_at
29
+ # @!attribute [r] end_at
30
30
  # @return [Time, nil] the end of the reporting interval.
31
- # @!attribute [rw] errors
31
+ # @!attribute [r] errors
32
32
  # @return [Array<String>] errors reported by the reporting organization.
33
- ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
33
+ ATTRIBUTES.each { |attribute| attr_reader(attribute) }
34
34
 
35
35
  def initialize(node)
36
36
  @node = node
37
- self.errors = []
37
+ @errors = []
38
38
 
39
39
  metadata = node.at_xpath('feedback/report_metadata')
40
40
  return unless metadata
@@ -42,13 +42,13 @@ module DMARCator
42
42
  metadata.element_children.each do |element|
43
43
  case element.name
44
44
  when 'org_name'
45
- self.org_name ||= element.text
45
+ @org_name ||= element.text
46
46
  when 'email'
47
- self.email ||= element.text
47
+ @email ||= element.text
48
48
  when 'extra_contact_info'
49
- self.extra_contact_info ||= element.text
49
+ @extra_contact_info ||= element.text
50
50
  when 'report_id'
51
- self.report_id ||= element.text
51
+ @report_id ||= element.text
52
52
  when 'error'
53
53
  errors << element.text
54
54
  when 'date_range'
@@ -63,9 +63,9 @@ module DMARCator
63
63
  date_range.element_children.each do |element|
64
64
  case element.name
65
65
  when 'begin'
66
- self.begin_at ||= parse_timestamp(element.text)
66
+ @begin_at ||= parse_timestamp(element.text)
67
67
  when 'end'
68
- self.end_at ||= parse_timestamp(element.text)
68
+ @end_at ||= parse_timestamp(element.text)
69
69
  end
70
70
  end
71
71
  end
@@ -13,21 +13,21 @@ module DMARCator
13
13
  # Attribute names exposed by this class.
14
14
  ATTRIBUTES = [:domain, :adkim, :aspf, :p, :sp, :pct, :fo]
15
15
 
16
- # @!attribute [rw] domain
16
+ # @!attribute [r] domain
17
17
  # @return [String, nil] the domain the policy applies to.
18
- # @!attribute [rw] adkim
18
+ # @!attribute [r] adkim
19
19
  # @return [String, nil] the DKIM alignment mode (`r` or `s`).
20
- # @!attribute [rw] aspf
20
+ # @!attribute [r] aspf
21
21
  # @return [String, nil] the SPF alignment mode (`r` or `s`).
22
- # @!attribute [rw] p
22
+ # @!attribute [r] p
23
23
  # @return [String, nil] the requested policy.
24
- # @!attribute [rw] sp
24
+ # @!attribute [r] sp
25
25
  # @return [String, nil] the requested subdomain policy.
26
- # @!attribute [rw] pct
26
+ # @!attribute [r] pct
27
27
  # @return [Integer, nil] the percentage of messages the policy applied to.
28
- # @!attribute [rw] fo
28
+ # @!attribute [r] fo
29
29
  # @return [String, nil] the requested failure-reporting options.
30
- ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
30
+ ATTRIBUTES.each { |attribute| attr_reader(attribute) }
31
31
 
32
32
  def initialize(node)
33
33
  @node = node
@@ -38,19 +38,19 @@ module DMARCator
38
38
  policy.element_children.each do |element|
39
39
  case element.name
40
40
  when 'pct'
41
- self.pct ||= parse_integer(element.text)
41
+ @pct ||= parse_integer(element.text)
42
42
  when 'domain'
43
- self.domain ||= element.text
43
+ @domain ||= element.text
44
44
  when 'adkim'
45
- self.adkim ||= element.text
45
+ @adkim ||= element.text
46
46
  when 'aspf'
47
- self.aspf ||= element.text
47
+ @aspf ||= element.text
48
48
  when 'p'
49
- self.p ||= element.text
49
+ @p ||= element.text
50
50
  when 'sp'
51
- self.sp ||= element.text
51
+ @sp ||= element.text
52
52
  when 'fo'
53
- self.fo ||= element.text
53
+ @fo ||= element.text
54
54
  end
55
55
  end
56
56
  end
@@ -13,11 +13,11 @@ module DMARCator
13
13
  # Attribute names exposed by this class.
14
14
  ATTRIBUTES = [:type, :comment]
15
15
 
16
- # @!attribute [rw] type
16
+ # @!attribute [r] type
17
17
  # @return [String, nil] the reason type.
18
- # @!attribute [rw] comment
18
+ # @!attribute [r] comment
19
19
  # @return [String, nil] an optional explanatory comment.
20
- ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
20
+ ATTRIBUTES.each { |attribute| attr_reader(attribute) }
21
21
 
22
22
  def initialize(node)
23
23
  @node = node
@@ -25,9 +25,9 @@ module DMARCator
25
25
  node.element_children.each do |element|
26
26
  case element.name
27
27
  when 'type'
28
- self.type ||= element.text
28
+ @type ||= element.text
29
29
  when 'comment'
30
- self.comment ||= element.text
30
+ @comment ||= element.text
31
31
  end
32
32
  end
33
33
  end
@@ -21,24 +21,23 @@ module DMARCator
21
21
  :envelope_to, :envelope_from, :header_from
22
22
  ]
23
23
 
24
- # @!attribute [rw] source_ip
24
+ # @!attribute [r] source_ip
25
25
  # @return [String, nil] the sending IP address.
26
- # @!attribute [rw] count
26
+ # @!attribute [r] count
27
27
  # @return [Integer, nil] the number of messages from this source.
28
- # @!attribute [rw] disposition
28
+ # @!attribute [r] disposition
29
29
  # @return [String, nil] the action applied to the messages.
30
- # @!attribute [rw] dkim
30
+ # @!attribute [r] dkim
31
31
  # @return [String, nil] the DKIM evaluation result.
32
- # @!attribute [rw] spf
32
+ # @!attribute [r] spf
33
33
  # @return [String, nil] the SPF evaluation result.
34
- # @!attribute [rw] envelope_to
34
+ # @!attribute [r] envelope_to
35
35
  # @return [String, nil] the envelope recipient domain.
36
- # @!attribute [rw] envelope_from
36
+ # @!attribute [r] envelope_from
37
37
  # @return [String, nil] the envelope sender domain.
38
- # @!attribute [rw] header_from
38
+ # @!attribute [r] header_from
39
39
  # @return [String, nil] the `From` header domain.
40
- ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
41
- attr_writer :auth_results, :reasons
40
+ ATTRIBUTES.each { |attribute| attr_reader(attribute) }
42
41
 
43
42
  def initialize(node)
44
43
  @node = node
@@ -87,9 +86,9 @@ module DMARCator
87
86
  row.element_children.each do |element|
88
87
  case element.name
89
88
  when 'source_ip'
90
- self.source_ip ||= element.text
89
+ @source_ip ||= element.text
91
90
  when 'count'
92
- self.count ||= parse_integer(element.text)
91
+ @count ||= parse_integer(element.text)
93
92
  when 'policy_evaluated'
94
93
  parse_policy_evaluated(element)
95
94
  end
@@ -100,11 +99,11 @@ module DMARCator
100
99
  policy_evaluated.element_children.each do |element|
101
100
  case element.name
102
101
  when 'disposition'
103
- self.disposition ||= element.text
102
+ @disposition ||= element.text
104
103
  when 'dkim'
105
- self.dkim ||= element.text
104
+ @dkim ||= element.text
106
105
  when 'spf'
107
- self.spf ||= element.text
106
+ @spf ||= element.text
108
107
  when 'reason'
109
108
  @reason_nodes << element
110
109
  end
@@ -115,11 +114,11 @@ module DMARCator
115
114
  identifiers.element_children.each do |element|
116
115
  case element.name
117
116
  when 'envelope_to'
118
- self.envelope_to ||= element.text
117
+ @envelope_to ||= element.text
119
118
  when 'envelope_from'
120
- self.envelope_from ||= element.text
119
+ @envelope_from ||= element.text
121
120
  when 'header_from'
122
- self.header_from ||= element.text
121
+ @header_from ||= element.text
123
122
  end
124
123
  end
125
124
  end
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'nokogiri'
4
- require 'bigdecimal'
5
- require 'bigdecimal/util'
6
4
 
5
+ require_relative 'config'
7
6
  require_relative 'xml_helper'
8
7
  require_relative 'exceptions'
9
8
  require_relative 'report/metadata'
@@ -21,22 +20,35 @@ module DMARCator
21
20
  include XmlHelper
22
21
 
23
22
  # Default maximum accepted report size in bytes (25 MiB).
24
- MAX_REPORT_BYTES = 25 * 1024 * 1024
23
+ #
24
+ # @deprecated Use {Config::DEFAULT_MAX_REPORT_BYTES}.
25
+ #
26
+ MAX_REPORT_BYTES = Config::DEFAULT_MAX_REPORT_BYTES
25
27
 
26
28
  class << self
27
29
  #
28
30
  # The maximum accepted report size in bytes.
29
31
  #
30
- # Defaults to {MAX_REPORT_BYTES}. Parsing a report builds a DOM that is
31
- # roughly 13x the input size, so lower this to bound memory for reports
32
- # from untrusted sources.
32
+ # @deprecated Use {DMARCator.configure} with `config.max_report_bytes`.
33
33
  #
34
34
  # @return [Integer]
35
35
  #
36
- attr_writer :max_report_bytes
37
-
38
36
  def max_report_bytes
39
- @max_report_bytes || MAX_REPORT_BYTES
37
+ DMARCator.config.max_report_bytes
38
+ end
39
+
40
+ #
41
+ # Sets the maximum accepted report size in bytes.
42
+ #
43
+ # @deprecated Use {DMARCator.configure} with `config.max_report_bytes`.
44
+ #
45
+ # @param [Integer, nil] value
46
+ #
47
+ # @raise [ArgumentError]
48
+ # The value is not a positive Integer or nil.
49
+ #
50
+ def max_report_bytes=(value)
51
+ DMARCator.config.max_report_bytes = value
40
52
  end
41
53
  end
42
54
 
@@ -47,8 +59,8 @@ module DMARCator
47
59
  # The raw DMARC XML report.
48
60
  #
49
61
  # @raise [InvalidReport]
50
- # The report is not a String, exceeds {.max_report_bytes}, or is not
51
- # well-formed XML.
62
+ # The report is not a String, exceeds
63
+ # {Config#max_report_bytes}, or is not well-formed XML.
52
64
  #
53
65
  def initialize(xml)
54
66
  unless xml.is_a?(String)
@@ -57,7 +69,7 @@ module DMARCator
57
69
 
58
70
  @xml = xml
59
71
 
60
- limit = self.class.max_report_bytes
72
+ limit = DMARCator.config.max_report_bytes
61
73
 
62
74
  if xml.bytesize > limit
63
75
  raise InvalidReport, "DMARC XML report exceeds maximum size of #{limit} bytes"
@@ -73,23 +85,22 @@ module DMARCator
73
85
  end
74
86
 
75
87
  #
76
- # The report version.
88
+ # The report version, as published.
89
+ #
90
+ # Returned as a String because versions are opaque identifiers, not
91
+ # numbers to compute with.
77
92
  #
78
- # @return [BigDecimal, nil]
93
+ # @return [String, nil]
94
+ # The version, or `nil` when it is absent or not numeric.
79
95
  #
80
96
  def version
81
97
  return @version if defined?(@version)
82
98
 
83
99
  @version = begin
84
100
  value = get_text('feedback/version')
101
+ value = value.strip if value
85
102
 
86
- if value && !value.empty?
87
- begin
88
- BigDecimal(value)
89
- rescue ArgumentError
90
- nil
91
- end
92
- end
103
+ value if value && value.match?(/\A\d+(?:\.\d+)*\z/)
93
104
  end
94
105
  end
95
106
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module DMARCator
4
4
  # dmarcator version
5
- VERSION = '0.11.0'
5
+ VERSION = '0.12.0'
6
6
  end
@@ -7,6 +7,8 @@ module DMARCator
7
7
  # Internal helper for extracting values from parsed DMARC XML reports.
8
8
  #
9
9
  module XmlHelper
10
+ private
11
+
10
12
  #
11
13
  # Returns the text of the first node matching the XPath expression.
12
14
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dmarcator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noah Berman
@@ -43,20 +43,6 @@ dependencies:
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
45
  version: '1.10'
46
- - !ruby/object:Gem::Dependency
47
- name: bigdecimal
48
- requirement: !ruby/object:Gem::Requirement
49
- requirements:
50
- - - ">="
51
- - !ruby/object:Gem::Version
52
- version: '1.0'
53
- type: :runtime
54
- prerelease: false
55
- version_requirements: !ruby/object:Gem::Requirement
56
- requirements:
57
- - - ">="
58
- - !ruby/object:Gem::Version
59
- version: '1.0'
60
46
  description: A hardened DMARC record parser and query client, forked from the Trail
61
47
  of Bits 'dmarc' gem.
62
48
  email: