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 +4 -4
- data/ChangeLog.md +22 -0
- data/README.md +31 -6
- data/lib/dmarcator/config.rb +39 -0
- data/lib/dmarcator/parser.rb +4 -0
- data/lib/dmarcator/record.rb +2 -0
- data/lib/dmarcator/report/auth_result.rb +11 -11
- data/lib/dmarcator/report/metadata.rb +15 -15
- data/lib/dmarcator/report/policy.rb +15 -15
- data/lib/dmarcator/report/reason.rb +5 -5
- data/lib/dmarcator/report/row.rb +17 -18
- data/lib/dmarcator/report.rb +32 -21
- data/lib/dmarcator/version.rb +1 -1
- data/lib/dmarcator/xml_helper.rb +2 -0
- metadata +1 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6c289ca56b406c5f591f414ada16407028e732310ad9b4c784e2f231469828a
|
|
4
|
+
data.tar.gz: 24f260da56f98e045d262bd5fcbe3ee9d3f8be1b1bdd310e26e8b9b4b74f1021
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
|
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
|
|
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
|
data/lib/dmarcator/config.rb
CHANGED
|
@@ -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
|
#
|
data/lib/dmarcator/parser.rb
CHANGED
data/lib/dmarcator/record.rb
CHANGED
|
@@ -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 [
|
|
16
|
+
# @!attribute [r] type
|
|
17
17
|
# @return [String] the entry name (`dkim` or `spf`).
|
|
18
|
-
# @!attribute [
|
|
18
|
+
# @!attribute [r] domain
|
|
19
19
|
# @return [String, nil] the authenticated domain.
|
|
20
|
-
# @!attribute [
|
|
20
|
+
# @!attribute [r] result
|
|
21
21
|
# @return [String, nil] the authentication result.
|
|
22
|
-
# @!attribute [
|
|
22
|
+
# @!attribute [r] selector
|
|
23
23
|
# @return [String, nil] the DKIM selector.
|
|
24
|
-
# @!attribute [
|
|
24
|
+
# @!attribute [r] scope
|
|
25
25
|
# @return [String, nil] the SPF scope.
|
|
26
|
-
ATTRIBUTES.each { |attribute|
|
|
26
|
+
ATTRIBUTES.each { |attribute| attr_reader(attribute) }
|
|
27
27
|
|
|
28
28
|
def initialize(node)
|
|
29
29
|
@node = node
|
|
30
|
-
|
|
30
|
+
@type = node.name
|
|
31
31
|
|
|
32
32
|
node.element_children.each do |element|
|
|
33
33
|
case element.name
|
|
34
34
|
when 'domain'
|
|
35
|
-
|
|
35
|
+
@domain ||= element.text
|
|
36
36
|
when 'result'
|
|
37
|
-
|
|
37
|
+
@result ||= element.text
|
|
38
38
|
when 'selector'
|
|
39
|
-
|
|
39
|
+
@selector ||= element.text
|
|
40
40
|
when 'scope'
|
|
41
|
-
|
|
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 [
|
|
19
|
+
# @!attribute [r] org_name
|
|
20
20
|
# @return [String, nil] the reporting organization.
|
|
21
|
-
# @!attribute [
|
|
21
|
+
# @!attribute [r] email
|
|
22
22
|
# @return [String, nil] the reporting organization's contact address.
|
|
23
|
-
# @!attribute [
|
|
23
|
+
# @!attribute [r] extra_contact_info
|
|
24
24
|
# @return [String, nil] additional contact information.
|
|
25
|
-
# @!attribute [
|
|
25
|
+
# @!attribute [r] report_id
|
|
26
26
|
# @return [String, nil] the unique report identifier.
|
|
27
|
-
# @!attribute [
|
|
27
|
+
# @!attribute [r] begin_at
|
|
28
28
|
# @return [Time, nil] the start of the reporting interval.
|
|
29
|
-
# @!attribute [
|
|
29
|
+
# @!attribute [r] end_at
|
|
30
30
|
# @return [Time, nil] the end of the reporting interval.
|
|
31
|
-
# @!attribute [
|
|
31
|
+
# @!attribute [r] errors
|
|
32
32
|
# @return [Array<String>] errors reported by the reporting organization.
|
|
33
|
-
ATTRIBUTES.each { |attribute|
|
|
33
|
+
ATTRIBUTES.each { |attribute| attr_reader(attribute) }
|
|
34
34
|
|
|
35
35
|
def initialize(node)
|
|
36
36
|
@node = node
|
|
37
|
-
|
|
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
|
-
|
|
45
|
+
@org_name ||= element.text
|
|
46
46
|
when 'email'
|
|
47
|
-
|
|
47
|
+
@email ||= element.text
|
|
48
48
|
when 'extra_contact_info'
|
|
49
|
-
|
|
49
|
+
@extra_contact_info ||= element.text
|
|
50
50
|
when 'report_id'
|
|
51
|
-
|
|
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
|
-
|
|
66
|
+
@begin_at ||= parse_timestamp(element.text)
|
|
67
67
|
when 'end'
|
|
68
|
-
|
|
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 [
|
|
16
|
+
# @!attribute [r] domain
|
|
17
17
|
# @return [String, nil] the domain the policy applies to.
|
|
18
|
-
# @!attribute [
|
|
18
|
+
# @!attribute [r] adkim
|
|
19
19
|
# @return [String, nil] the DKIM alignment mode (`r` or `s`).
|
|
20
|
-
# @!attribute [
|
|
20
|
+
# @!attribute [r] aspf
|
|
21
21
|
# @return [String, nil] the SPF alignment mode (`r` or `s`).
|
|
22
|
-
# @!attribute [
|
|
22
|
+
# @!attribute [r] p
|
|
23
23
|
# @return [String, nil] the requested policy.
|
|
24
|
-
# @!attribute [
|
|
24
|
+
# @!attribute [r] sp
|
|
25
25
|
# @return [String, nil] the requested subdomain policy.
|
|
26
|
-
# @!attribute [
|
|
26
|
+
# @!attribute [r] pct
|
|
27
27
|
# @return [Integer, nil] the percentage of messages the policy applied to.
|
|
28
|
-
# @!attribute [
|
|
28
|
+
# @!attribute [r] fo
|
|
29
29
|
# @return [String, nil] the requested failure-reporting options.
|
|
30
|
-
ATTRIBUTES.each { |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
|
-
|
|
41
|
+
@pct ||= parse_integer(element.text)
|
|
42
42
|
when 'domain'
|
|
43
|
-
|
|
43
|
+
@domain ||= element.text
|
|
44
44
|
when 'adkim'
|
|
45
|
-
|
|
45
|
+
@adkim ||= element.text
|
|
46
46
|
when 'aspf'
|
|
47
|
-
|
|
47
|
+
@aspf ||= element.text
|
|
48
48
|
when 'p'
|
|
49
|
-
|
|
49
|
+
@p ||= element.text
|
|
50
50
|
when 'sp'
|
|
51
|
-
|
|
51
|
+
@sp ||= element.text
|
|
52
52
|
when 'fo'
|
|
53
|
-
|
|
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 [
|
|
16
|
+
# @!attribute [r] type
|
|
17
17
|
# @return [String, nil] the reason type.
|
|
18
|
-
# @!attribute [
|
|
18
|
+
# @!attribute [r] comment
|
|
19
19
|
# @return [String, nil] an optional explanatory comment.
|
|
20
|
-
ATTRIBUTES.each { |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
|
-
|
|
28
|
+
@type ||= element.text
|
|
29
29
|
when 'comment'
|
|
30
|
-
|
|
30
|
+
@comment ||= element.text
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
data/lib/dmarcator/report/row.rb
CHANGED
|
@@ -21,24 +21,23 @@ module DMARCator
|
|
|
21
21
|
:envelope_to, :envelope_from, :header_from
|
|
22
22
|
]
|
|
23
23
|
|
|
24
|
-
# @!attribute [
|
|
24
|
+
# @!attribute [r] source_ip
|
|
25
25
|
# @return [String, nil] the sending IP address.
|
|
26
|
-
# @!attribute [
|
|
26
|
+
# @!attribute [r] count
|
|
27
27
|
# @return [Integer, nil] the number of messages from this source.
|
|
28
|
-
# @!attribute [
|
|
28
|
+
# @!attribute [r] disposition
|
|
29
29
|
# @return [String, nil] the action applied to the messages.
|
|
30
|
-
# @!attribute [
|
|
30
|
+
# @!attribute [r] dkim
|
|
31
31
|
# @return [String, nil] the DKIM evaluation result.
|
|
32
|
-
# @!attribute [
|
|
32
|
+
# @!attribute [r] spf
|
|
33
33
|
# @return [String, nil] the SPF evaluation result.
|
|
34
|
-
# @!attribute [
|
|
34
|
+
# @!attribute [r] envelope_to
|
|
35
35
|
# @return [String, nil] the envelope recipient domain.
|
|
36
|
-
# @!attribute [
|
|
36
|
+
# @!attribute [r] envelope_from
|
|
37
37
|
# @return [String, nil] the envelope sender domain.
|
|
38
|
-
# @!attribute [
|
|
38
|
+
# @!attribute [r] header_from
|
|
39
39
|
# @return [String, nil] the `From` header domain.
|
|
40
|
-
ATTRIBUTES.each { |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
|
-
|
|
89
|
+
@source_ip ||= element.text
|
|
91
90
|
when 'count'
|
|
92
|
-
|
|
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
|
-
|
|
102
|
+
@disposition ||= element.text
|
|
104
103
|
when 'dkim'
|
|
105
|
-
|
|
104
|
+
@dkim ||= element.text
|
|
106
105
|
when 'spf'
|
|
107
|
-
|
|
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
|
-
|
|
117
|
+
@envelope_to ||= element.text
|
|
119
118
|
when 'envelope_from'
|
|
120
|
-
|
|
119
|
+
@envelope_from ||= element.text
|
|
121
120
|
when 'header_from'
|
|
122
|
-
|
|
121
|
+
@header_from ||= element.text
|
|
123
122
|
end
|
|
124
123
|
end
|
|
125
124
|
end
|
data/lib/dmarcator/report.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 [
|
|
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 &&
|
|
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
|
|
data/lib/dmarcator/version.rb
CHANGED
data/lib/dmarcator/xml_helper.rb
CHANGED
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.
|
|
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:
|