dmarcator 0.4.0 → 0.4.2
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 +16 -0
- data/README.md +0 -6
- data/lib/dmarcator/parser.rb +20 -5
- data/lib/dmarcator/query.rb +9 -2
- data/lib/dmarcator/record.rb +2 -2
- data/lib/dmarcator/report.rb +9 -1
- data/lib/dmarcator/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bf9c3559d1b305ece3b68de7e2e0eb72f1371be508b7499ce34160e543cbd6a1
|
|
4
|
+
data.tar.gz: f082d761778abd9e66e8bed82cee7a3aafc498567814021120a5780dc9c5f5fc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 85da1dd8330918fb7ae6c167a9c08b23c97589f5fb3d8586c7866e64d19cd9bdf04a39efdfa161be70c01ef276cadc3b907e04b35df3ed10f9c32ffd28eb2be8
|
|
7
|
+
data.tar.gz: 4ed5b91fa75ba6eda666357a1541e27b11d008c5ec952f9573cf3a89243877a9757d09b6ae7d0488bb449b6c6dc798ed594d9ab75de1d911490e86bcda82a041
|
data/ChangeLog.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
### 0.4.2 / 2026-10-28
|
|
2
|
+
|
|
3
|
+
* Do not raise when the loaded `resolv` version is not a valid gem version; an unparseable version is treated as unknown instead of failing at load time.
|
|
4
|
+
* Reject DMARC records larger than 4 KiB (`Parser::MAX_RECORD_BYTES`) before parsing to bound work on hostile input.
|
|
5
|
+
* Raise the documented `InvalidRecord`/`InvalidReport` for non-String input instead of `ArgumentError`, `TypeError`, or a silently empty report.
|
|
6
|
+
|
|
7
|
+
### 0.4.1 / 2026-10-28
|
|
8
|
+
|
|
9
|
+
* Accept a case-insensitive `mailto:` scheme in `rua`/`ruf` report URIs.
|
|
10
|
+
* Fix parsing of `rua`/`ruf` lists that mix valid and invalid URIs.
|
|
11
|
+
* Restrict `mailto:` address characters to a safe whitelist.
|
|
12
|
+
* Reject underscores in validated domain names.
|
|
13
|
+
* Remove the dead Alexa Top 500 scraping task and its fixture.
|
|
14
|
+
* Drop the `simplecov` development dependency.
|
|
15
|
+
* Housekeeping: remove dead rescue code, fix documentation typos, add frozen string literals, make `Uri` delegation private, and drop the deprecated `gem.test_files`.
|
|
16
|
+
|
|
1
17
|
### 0.4.0 / 2026-10-26
|
|
2
18
|
|
|
3
19
|
* Add `strict: true` parsing mode to reject unknown tags and invalid tag values.
|
data/README.md
CHANGED
|
@@ -150,12 +150,6 @@ To run the RSpec tests:
|
|
|
150
150
|
rake spec
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
-
To test the parser against the bundled Alexa Top 500 fixture:
|
|
154
|
-
|
|
155
|
-
```sh
|
|
156
|
-
rake spec:gauntlet
|
|
157
|
-
```
|
|
158
|
-
|
|
159
153
|
## License
|
|
160
154
|
|
|
161
155
|
This project is released under the MIT License. See the [LICENSE.txt] file for
|
data/lib/dmarcator/parser.rb
CHANGED
|
@@ -14,6 +14,11 @@ module DMARCator
|
|
|
14
14
|
#
|
|
15
15
|
class Parser < Parslet::Parser
|
|
16
16
|
|
|
17
|
+
# Maximum accepted DMARC record size in bytes. Real records are far
|
|
18
|
+
# smaller; the cap bounds parsing work on hostile input.
|
|
19
|
+
#
|
|
20
|
+
MAX_RECORD_BYTES = 4096
|
|
21
|
+
|
|
17
22
|
root :dmarc_record
|
|
18
23
|
|
|
19
24
|
rule(:dmarc_record) do
|
|
@@ -67,13 +72,13 @@ module DMARCator
|
|
|
67
72
|
end
|
|
68
73
|
|
|
69
74
|
tag_rule(:auri, 'rua') do
|
|
70
|
-
dmarc_uri >> (
|
|
75
|
+
dmarc_uri >> (uri_list_sep.present? >> uri_list_sep).repeat >> (wsp? >> str(',')).absent?
|
|
71
76
|
end
|
|
72
77
|
|
|
73
78
|
tag_rule(:ainterval,'ri') { digit.repeat(1, 10) >> digit.absent? }
|
|
74
79
|
|
|
75
80
|
tag_rule(:furi,'ruf') do
|
|
76
|
-
dmarc_uri >> (
|
|
81
|
+
dmarc_uri >> (uri_list_sep.present? >> uri_list_sep).repeat >> (wsp? >> str(',')).absent?
|
|
77
82
|
end
|
|
78
83
|
|
|
79
84
|
tag_rule(:fo,'fo') do
|
|
@@ -99,6 +104,8 @@ module DMARCator
|
|
|
99
104
|
).maybe >> str('!').absent?
|
|
100
105
|
end
|
|
101
106
|
|
|
107
|
+
rule(:uri_list_sep) { wsp? >> str(',') >> wsp? >> dmarc_uri }
|
|
108
|
+
|
|
102
109
|
rule(:mailto_uri) do
|
|
103
110
|
match['mM'] >> match['aA'] >> match['iI'] >> match['lL'] >> match['tT'] >> match['oO'] >>
|
|
104
111
|
str(':') >> mailto_address
|
|
@@ -110,7 +117,7 @@ module DMARCator
|
|
|
110
117
|
|
|
111
118
|
rule(:mailto_local) { mailto_char.repeat(1) }
|
|
112
119
|
rule(:mailto_domain) { mailto_char.repeat(1) }
|
|
113
|
-
rule(:mailto_char) { match["
|
|
120
|
+
rule(:mailto_char) { match["A-Za-z0-9._%+-"] }
|
|
114
121
|
|
|
115
122
|
rule(:digit) { match('[0-9]') }
|
|
116
123
|
rule(:wsp) { str(' ') | str("\t") }
|
|
@@ -192,10 +199,18 @@ module DMARCator
|
|
|
192
199
|
# The Hash of tags within the record.
|
|
193
200
|
#
|
|
194
201
|
# @raise [InvalidRecord]
|
|
195
|
-
# The record is malformed, or (in strict
|
|
196
|
-
# or invalid tag value.
|
|
202
|
+
# The record is not a String, is malformed, is too large, or (in strict
|
|
203
|
+
# mode) contains an unknown tag or invalid tag value.
|
|
197
204
|
#
|
|
198
205
|
def self.parse(record, strict: false)
|
|
206
|
+
unless record.is_a?(String)
|
|
207
|
+
raise InvalidRecord, "DMARC record must be a String, got #{record.class}"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
if record.bytesize > MAX_RECORD_BYTES
|
|
211
|
+
raise InvalidRecord, "DMARC record exceeds maximum size of #{MAX_RECORD_BYTES} bytes"
|
|
212
|
+
end
|
|
213
|
+
|
|
199
214
|
tree = new.parse(record)
|
|
200
215
|
|
|
201
216
|
if strict
|
data/lib/dmarcator/query.rb
CHANGED
|
@@ -40,8 +40,15 @@ module DMARCator
|
|
|
40
40
|
version = resolv_version
|
|
41
41
|
return false unless version
|
|
42
42
|
|
|
43
|
+
begin
|
|
44
|
+
parsed = Gem::Version.new(version)
|
|
45
|
+
rescue ArgumentError
|
|
46
|
+
# A non-semver version string means we cannot tell; do not warn.
|
|
47
|
+
return false
|
|
48
|
+
end
|
|
49
|
+
|
|
43
50
|
VULNERABLE_RESOLV.any? do |requirement|
|
|
44
|
-
requirement.satisfied_by?(
|
|
51
|
+
requirement.satisfied_by?(parsed)
|
|
45
52
|
end
|
|
46
53
|
end
|
|
47
54
|
|
|
@@ -62,7 +69,7 @@ module DMARCator
|
|
|
62
69
|
domain.length <= 248 &&
|
|
63
70
|
!domain.start_with?('.') &&
|
|
64
71
|
!domain.include?('..') &&
|
|
65
|
-
!domain.match?(/[^A-Za-z0-9
|
|
72
|
+
!domain.match?(/[^A-Za-z0-9.-]/) &&
|
|
66
73
|
domain.split('.').all? { |label| label.length <= 63 && !label.start_with?('-') && !label.end_with?('-') }
|
|
67
74
|
raise ArgumentError, "invalid domain: #{domain.inspect}"
|
|
68
75
|
end
|
data/lib/dmarcator/record.rb
CHANGED
|
@@ -44,7 +44,7 @@ module DMARCator
|
|
|
44
44
|
#
|
|
45
45
|
# @option attributes [:r, :s] :aspf (:r)
|
|
46
46
|
#
|
|
47
|
-
# @option attributes [Array<
|
|
47
|
+
# @option attributes [Array<String>] :fo (['0'])
|
|
48
48
|
#
|
|
49
49
|
# @option attributes [:none, :quarantine, :reject] :p
|
|
50
50
|
#
|
|
@@ -153,7 +153,7 @@ module DMARCator
|
|
|
153
153
|
#
|
|
154
154
|
# `fo` field.
|
|
155
155
|
#
|
|
156
|
-
# @return [Array<
|
|
156
|
+
# @return [Array<String>]
|
|
157
157
|
# The value of the `fo=` field, or `["0"]` if the field was omitted.
|
|
158
158
|
#
|
|
159
159
|
def fo
|
data/lib/dmarcator/report.rb
CHANGED
|
@@ -29,10 +29,18 @@ module DMARCator
|
|
|
29
29
|
# @param [String] xml
|
|
30
30
|
# The raw DMARC XML report.
|
|
31
31
|
#
|
|
32
|
+
# @raise [InvalidReport]
|
|
33
|
+
# The report is not a String, exceeds {MAX_REPORT_BYTES}, or is not
|
|
34
|
+
# well-formed XML.
|
|
35
|
+
#
|
|
32
36
|
def initialize(xml)
|
|
37
|
+
unless xml.is_a?(String)
|
|
38
|
+
raise InvalidReport, "DMARC XML report must be a String, got #{xml.class}"
|
|
39
|
+
end
|
|
40
|
+
|
|
33
41
|
@xml = xml
|
|
34
42
|
|
|
35
|
-
if xml.
|
|
43
|
+
if xml.bytesize > MAX_REPORT_BYTES
|
|
36
44
|
raise InvalidReport, "DMARC XML report exceeds maximum size of #{MAX_REPORT_BYTES} bytes"
|
|
37
45
|
end
|
|
38
46
|
|
data/lib/dmarcator/version.rb
CHANGED