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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c4ed90f1362c3a69141cadeb2e7360b71a7f86c18f74f433990d0760d2bab0d
4
- data.tar.gz: 63d27b1fc2acbf70627757d7a5604c1cf471e2d2f7dc71c80e8e221f663f3a70
3
+ metadata.gz: bf9c3559d1b305ece3b68de7e2e0eb72f1371be508b7499ce34160e543cbd6a1
4
+ data.tar.gz: f082d761778abd9e66e8bed82cee7a3aafc498567814021120a5780dc9c5f5fc
5
5
  SHA512:
6
- metadata.gz: 92f2851179be4e2ee8eaaa542afa87ab6efcb2aed546161001778efda5f41579a78a552b1a30010ff625f877f94cbf08c7b7506ffbdacc06fc1d68c9753ef415
7
- data.tar.gz: 10ad6dc240c07a14705ac06c83388c0981b4476ae03c980323b0d288f69693ea552218b94615524c9c7b8692b521a316157fd0c3fed87f96726acf2dfc7ce4b8
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
@@ -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 >> (wsp? >> str(',') >> wsp? >> dmarc_uri).repeat
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 >> (wsp? >> str(',') >> wsp? >> dmarc_uri).repeat
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["^;,! \t@"] }
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 mode) contains an unknown tag
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
@@ -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?(Gem::Version.new(version))
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
@@ -44,7 +44,7 @@ module DMARCator
44
44
  #
45
45
  # @option attributes [:r, :s] :aspf (:r)
46
46
  #
47
- # @option attributes [Array<'0', '1', 'd', 's'>] :fo ('0')
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<'0', '1', 'd', 's'>]
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
@@ -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.is_a?(String) && xml.bytesize > MAX_REPORT_BYTES
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
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module DMARCator
4
4
  # dmarcator version
5
- VERSION = '0.4.0'
5
+ VERSION = '0.4.2'
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.4.0
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noah Berman