dmarcator 0.9.0 → 0.11.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: f4c6e25ca7a0fc2d77204c72ebb01906fce1ac4b537b1e011e505ed2938dda2b
4
- data.tar.gz: 36f390d4c0e42c5e938df0345e9d412de2b09aed29da9ca83a424414090b88f1
3
+ metadata.gz: 689cd037f395f7803f4157fd148305f1d9d6c3dbb19fd42b61311b8e37d57124
4
+ data.tar.gz: 80518a23a140cb7102cb4d20a5fa896852ca8b104a3047939bcf4ba018e89699
5
5
  SHA512:
6
- metadata.gz: 8f3db5da3231a84613e3a938d3e294f01da16df7aee2e633ba321e0002a8278ec28755accb08a1db2e6c72f2d0424cb34ea019b3168b32872503f59dddb6eaa4
7
- data.tar.gz: 28d72632daef6199571fd1cd461c112610ebd14bd45a03b48c6b9127f571e82f8431ad35331e4c05d52482b2b5d085bb973a51aa37a888ede498b8b7a85f4eac
6
+ metadata.gz: c3b5f59702e05d0ffeeec17c9c3e7f8c1bd078448ba029fd4cf6bc8e6cbd8107dbda2e3633c228d1706f09832023a8a64b9806d6ee843b1fe13d449b19379e0c
7
+ data.tar.gz: de98c5111a0eb8023fa10807c7227d18bead12e17f2f43d084d8c5e3d27a2b1a4e86212c0c075fa2e75a97e5710e738de2baad45b833c31249d54a1abec80bb2
data/ChangeLog.md CHANGED
@@ -1,3 +1,27 @@
1
+ ### 0.11.0 / 2026-10-29
2
+
3
+ * 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.
4
+ * A configured resolver may be a callable, in which case it is invoked for each lookup. This lets threaded applications use one resolver per lookup instead of sharing a `Resolv::DNS`, which does not serialise concurrent lookups.
5
+ * Run CI on macOS 15 as well as Ubuntu 24.04, covering the Socket/OpenSSL and Net::HTTP paths used by the DoT and DoH transports.
6
+
7
+ ### 0.10.0 / 2026-10-29
8
+
9
+ Breaking changes, taken before 1.0 so the release does not carry them:
10
+
11
+ * Add `DMARCator::Error` as the base class for every error the library raises. `InvalidRecord`, `InvalidReport` and `ResolutionError` inherit from it, so one `rescue DMARCator::Error` catches all three.
12
+ * `InvalidRecord` no longer inherits from `Parslet::ParseFailed`, removing the parser implementation from the public exception contract. Parse failures still expose the underlying parser error as their `cause`.
13
+ * Remove the deprecated `DMARCator.query` and `DMARCator::Record.query` aliases. Use `DMARCator.dmarc` and `DMARCator::Record.lookup`.
14
+ * Rename `DMARCator::Report::Record` to `DMARCator::Report::Row`, so aggregate rows are not confused with `DMARCator::Record`, the parsed DMARC TXT record.
15
+
16
+ Migration:
17
+
18
+ | Before | After |
19
+ |---|---|
20
+ | `DMARCator.query(domain)` | `DMARCator.dmarc(domain)` |
21
+ | `DMARCator::Record.query(domain)` | `DMARCator::Record.lookup(domain)` |
22
+ | `DMARCator::Report::Record` | `DMARCator::Report::Row` |
23
+ | `rescue Parslet::ParseFailed` | `rescue DMARCator::Error` |
24
+
1
25
  ### 0.9.0 / 2026-10-29
2
26
 
3
27
  * Add `DMARCator.txt_records`, which returns the TXT resource records instead of strings, so callers that cache lookups can read each record's `ttl`. `DMARCator.txt` is now defined in terms of it and behaves the same.
data/README.md CHANGED
@@ -177,6 +177,47 @@ more than the lookup on a fast resolver); `Resolv::DNS` does not lock per query,
177
177
  so use one resolver per thread when parallelising; and DoT/DoH open a new TLS
178
178
  connection per lookup, so plaintext is the better choice for bulk work.
179
179
 
180
+ ### SPF and DKIM
181
+
182
+ The TXT primitives also cover the other two records a checker needs. There is
183
+ no record-type-specific API, because the semantics belong to the caller:
184
+
185
+ ```ruby
186
+ DMARCator.txt("example.com") # SPF (RFC 7208)
187
+ DMARCator.txt("s1._domainkey.example.com") # DKIM (RFC 6376)
188
+ ```
189
+
190
+ Both need the raw list rather than a single value: more than one SPF record is
191
+ a `permerror`, and DKIM keys can be duplicated, rotated or revoked (`p=`). A
192
+ helper returning "the" record would hide exactly those conditions. DKIM is also
193
+ per-selector and selectors cannot be enumerated, so any convenience method would
194
+ have to take a selector and would only build the name above.
195
+
196
+ ## Configuration
197
+
198
+ Process-wide defaults can be set from code, and take precedence over the
199
+ `DMARCATOR_DNS` environment variables:
200
+
201
+ ```ruby
202
+ DMARCator.configure do |config|
203
+ config.resolver = DMARCator::Resolver::DoH.new(url: "https://dns.example/dns-query")
204
+ end
205
+ ```
206
+
207
+ A resolver object is shared by every thread, and `Resolv::DNS` does not
208
+ serialise concurrent lookups, so threaded applications should configure a
209
+ factory instead:
210
+
211
+ ```ruby
212
+ DMARCator.configure do |config|
213
+ config.resolver = -> { Resolv::DNS.new }
214
+ end
215
+ ```
216
+
217
+ `DMARCator.reset_configuration!` discards configured values, which is useful in
218
+ tests. Passing `resolver:` to a lookup still overrides both the configured
219
+ default and the environment.
220
+
180
221
  ## Strict parsing
181
222
 
182
223
  By default, unknown tags and tags with invalid values are ignored. Pass
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DMARCator
4
+ #
5
+ # Process-wide defaults.
6
+ #
7
+ # @since 0.11.0
8
+ #
9
+ class Config
10
+ #
11
+ # The default resolver used when a lookup is not given one.
12
+ #
13
+ # Either a resolver ({Resolv::DNS}, {Resolver::DoH}, {Resolver::DoT}) or a
14
+ # callable returning one. Prefer a callable in threaded applications: a
15
+ # resolver object is shared by every thread, and `Resolv::DNS` does not
16
+ # serialise concurrent lookups.
17
+ #
18
+ # @return [Object, #call, nil]
19
+ #
20
+ attr_accessor :resolver
21
+ end
22
+
23
+ #
24
+ # The process-wide configuration.
25
+ #
26
+ # @return [Config]
27
+ #
28
+ # @since 0.11.0
29
+ #
30
+ def self.config
31
+ @config ||= Config.new
32
+ end
33
+
34
+ #
35
+ # Configures process-wide defaults.
36
+ #
37
+ # DMARCator.configure do |config|
38
+ # config.resolver = -> { Resolv::DNS.new }
39
+ # end
40
+ #
41
+ # When set, a configured resolver takes precedence over `DMARCATOR_DNS` and
42
+ # is used by {txt}, {txt_records}, {dmarc} and {Record.lookup} whenever no
43
+ # resolver is passed explicitly.
44
+ #
45
+ # @yieldparam [Config] config
46
+ #
47
+ # @return [Config]
48
+ #
49
+ # @since 0.11.0
50
+ #
51
+ def self.configure
52
+ yield config if block_given?
53
+ config
54
+ end
55
+
56
+ #
57
+ # Discards any configured defaults.
58
+ #
59
+ # @return [Config]
60
+ #
61
+ # @since 0.11.0
62
+ #
63
+ def self.reset_configuration!
64
+ @config = Config.new
65
+ end
66
+ end
@@ -1,30 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'parslet'
4
-
5
3
  module DMARCator
4
+ #
5
+ # Base class for every error raised by this library.
6
+ #
7
+ # Rescuing this catches parse failures, report failures and DNS lookup
8
+ # failures alike.
9
+ #
10
+ # @since 0.10.0
11
+ #
12
+ class Error < StandardError
13
+ end
14
+
6
15
  #
7
16
  # Raised when a DMARC record cannot be parsed.
8
17
  #
9
18
  # @since 0.2.0
10
19
  #
11
- class InvalidRecord < Parslet::ParseFailed
20
+ class InvalidRecord < Error
12
21
  end
13
22
 
14
23
  #
15
24
  # Raised when a DMARC XML report cannot be parsed.
16
25
  #
17
- class InvalidReport < StandardError
26
+ # @since 0.3.0
27
+ #
28
+ class InvalidReport < Error
18
29
  end
19
30
 
20
31
  #
21
- # Raised when an encrypted DNS transport fails to return a usable answer.
32
+ # Raised when a DNS transport fails to return a usable answer.
22
33
  #
23
34
  # This is deliberately distinct from a missing record: a failed lookup must
24
35
  # not be mistaken for a domain without a DMARC policy.
25
36
  #
26
37
  # @since 0.6.0
27
38
  #
28
- class ResolutionError < StandardError
39
+ class ResolutionError < Error
29
40
  end
30
41
  end
@@ -3,6 +3,7 @@
3
3
  require 'resolv'
4
4
  require 'rubygems'
5
5
 
6
+ require_relative 'config'
6
7
  require_relative 'resolver'
7
8
 
8
9
  module DMARCator
@@ -125,7 +126,11 @@ module DMARCator
125
126
  end
126
127
 
127
128
  #
128
- # Builds the resolver configured by the environment.
129
+ # The default resolver: a {configure}d one, or one built from the
130
+ # environment.
131
+ #
132
+ # A configured resolver may be an object or a callable; a callable is called
133
+ # for each lookup, which is what threaded applications should use.
129
134
  #
130
135
  # @return [Resolv::DNS, Resolver::DoH, Resolver::DoT]
131
136
  #
@@ -135,7 +140,11 @@ module DMARCator
135
140
  # @since 0.6.0
136
141
  #
137
142
  def self.default_resolver
138
- Resolver.build
143
+ configured = config.resolver
144
+
145
+ return configured.call if configured.respond_to?(:call)
146
+
147
+ configured || Resolver.build
139
148
  end
140
149
 
141
150
  #
@@ -246,16 +255,6 @@ module DMARCator
246
255
  dmarc_records.first if dmarc_records.one?
247
256
  end
248
257
 
249
- #
250
- # @deprecated Use {dmarc}, which names what it returns. Kept for
251
- # compatibility; it will be removed in a future release.
252
- #
253
- # @see dmarc
254
- #
255
- def self.query(domain,resolver=default_resolver)
256
- dmarc(domain, resolver: resolver)
257
- end
258
-
259
258
  #
260
259
  # Looks up the TXT records at an already-validated name.
261
260
  #
@@ -285,7 +285,8 @@ module DMARCator
285
285
 
286
286
  new(attributes)
287
287
  rescue Parslet::ParseFailed => error
288
- raise InvalidRecord.new(truncate_error_message(error.message), error.cause)
288
+ # Raising inside the rescue sets this error as the cause automatically.
289
+ raise InvalidRecord, truncate_error_message(error.message)
289
290
  end
290
291
 
291
292
  # Maximum length of error messages echoed from parsing untrusted input.
@@ -342,16 +343,6 @@ module DMARCator
342
343
  end
343
344
  end
344
345
 
345
- #
346
- # @deprecated Use {lookup}, which names what it returns. Kept for
347
- # compatibility; it will be removed in a future release.
348
- #
349
- # @see lookup
350
- #
351
- def self.query(domain,resolver=DMARCator.default_resolver)
352
- lookup(domain, resolver: resolver)
353
- end
354
-
355
346
  #
356
347
  # Converts the record to a Hash.
357
348
  #
@@ -9,7 +9,10 @@ module DMARCator
9
9
  #
10
10
  # Represents a single `record` element of a DMARC XML report.
11
11
  #
12
- class Record
12
+ # Named `Row` so it is not confused with {DMARCator::Record}, the parsed
13
+ # DMARC TXT record.
14
+ #
15
+ class Row
13
16
  include XmlHelper
14
17
 
15
18
  # Attribute names exposed by this class.
@@ -8,7 +8,7 @@ require_relative 'xml_helper'
8
8
  require_relative 'exceptions'
9
9
  require_relative 'report/metadata'
10
10
  require_relative 'report/policy'
11
- require_relative 'report/record'
11
+ require_relative 'report/row'
12
12
 
13
13
  module DMARCator
14
14
  #
@@ -114,11 +114,11 @@ module DMARCator
114
114
  #
115
115
  # The report records.
116
116
  #
117
- # @return [Array<Report::Record>]
117
+ # @return [Array<Report::Row>]
118
118
  #
119
119
  def records
120
120
  @records ||= @node.xpath('feedback/record').map do |node|
121
- Record.new(node)
121
+ Row.new(node)
122
122
  end
123
123
  end
124
124
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module DMARCator
4
4
  # dmarcator version
5
- VERSION = '0.9.0'
5
+ VERSION = '0.11.0'
6
6
  end
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.9.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noah Berman
@@ -69,6 +69,7 @@ files:
69
69
  - LICENSE.txt
70
70
  - README.md
71
71
  - lib/dmarcator.rb
72
+ - lib/dmarcator/config.rb
72
73
  - lib/dmarcator/exceptions.rb
73
74
  - lib/dmarcator/parser.rb
74
75
  - lib/dmarcator/query.rb
@@ -78,7 +79,7 @@ files:
78
79
  - lib/dmarcator/report/metadata.rb
79
80
  - lib/dmarcator/report/policy.rb
80
81
  - lib/dmarcator/report/reason.rb
81
- - lib/dmarcator/report/record.rb
82
+ - lib/dmarcator/report/row.rb
82
83
  - lib/dmarcator/resolver.rb
83
84
  - lib/dmarcator/resolver/doh.rb
84
85
  - lib/dmarcator/resolver/dot.rb