mailertogo-spf 0.2.0 → 0.2.1
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 +30 -0
- data/README.md +16 -0
- data/lib/mailertogo/spf/chain_audit.rb +28 -7
- data/lib/mailertogo/spf/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: 316234e7966e3e895703bbabc960f8cda0b932d7348e654d7afaf1d97c3ee094
|
|
4
|
+
data.tar.gz: 3a20869ecc92f35b8780672af93e7d38223cbc7e6ae6ca7913b678fae541c37c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d04d30b6ec8059c57d6231ffb786ffb4ce0ad8672b6faad7a29b8f215adbee0c97a3cbeb39078a24ac3988d25d273a6b05b77b02fff51a2be146110ee28927a1
|
|
7
|
+
data.tar.gz: 58c318462a651629b0cf7d2f11951dcb419486549a947e82fe5f6195c67f6fc694b636e66686bb87aa4b6aff2c13605f94749f3a6c754795a94f5bd15cdeadfb
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,36 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.2.1] - 2026-08-17
|
|
8
|
+
|
|
9
|
+
Two `ChainAudit` results could be read as facts when they were really "we did
|
|
10
|
+
not look". Both are corrected in the direction of admitting it.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- `ChainAudit#total` (and `#headroom`) are **`nil` rather than `0`** for a name
|
|
15
|
+
that publishes no record, or when DNS did not answer. `0` is a legitimate
|
|
16
|
+
total — `v=spf1 -all` costs exactly that — so a caller that skipped
|
|
17
|
+
`#published?` could report "this record costs 0 lookups" about a domain with
|
|
18
|
+
no record. A record that legitimately costs nothing still reports `0`.
|
|
19
|
+
- Duplicate records **at the hostname itself** are reported by the new
|
|
20
|
+
`#apex_duplicated?` instead of being pushed onto `#duplicated_in_chain`. RFC
|
|
21
|
+
7208 §4.5 makes them a permerror either way, but the apex is not *in* the
|
|
22
|
+
chain — it is the record being priced. Folding them together made the same
|
|
23
|
+
domain audit differently depending on whether the caller supplied `record:` or
|
|
24
|
+
let the gem resolve the apex, and a caller raising its own duplicate-record
|
|
25
|
+
defect from the records it resolved would have raised it twice.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- `ChainAudit#apex_duplicated?` — `true`/`false` when the gem resolved the apex,
|
|
30
|
+
and `nil` ("cannot say") when the record was supplied or DNS did not answer.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- `#duplicated_in_chain` no longer contains the audited hostname. Callers that
|
|
35
|
+
want that fact ask `#apex_duplicated?`.
|
|
36
|
+
|
|
7
37
|
## [0.2.0] - 2026-08-17
|
|
8
38
|
|
|
9
39
|
Answers a second question about a record: not only "does it authorize me, and
|
data/README.md
CHANGED
|
@@ -227,6 +227,22 @@ permerrors the whole evaluation rather than quietly doing nothing:
|
|
|
227
227
|
```ruby
|
|
228
228
|
audit.targets_without_spf # => ["nothing.example.net"] — include: of a name with no SPF (§5.2)
|
|
229
229
|
audit.duplicated_in_chain # => ["two.example.net"] — two v=spf1 records in the chain (§4.5)
|
|
230
|
+
audit.apex_duplicated? # => false — two at the hostname itself (§4.5)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`apex_duplicated?` is deliberately separate from `duplicated_in_chain`: the
|
|
234
|
+
hostname is not *in* the chain, it is the record being priced. It answers `nil`
|
|
235
|
+
— "cannot say" — when you supplied the `record:` yourself, because then we never
|
|
236
|
+
looked at the apex. A caller that resolved the apex already knows, and should
|
|
237
|
+
report from what it saw rather than ask twice.
|
|
238
|
+
|
|
239
|
+
A record that does not exist cannot be priced, so `total` and `headroom` are
|
|
240
|
+
`nil` rather than `0`. Check `published?` first:
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
audit = MailerToGo::SPF.chain_audit("no-spf.example.com")
|
|
244
|
+
audit.published? # => false
|
|
245
|
+
audit.total # => nil — not 0; `v=spf1 -all` legitimately costs 0
|
|
230
246
|
```
|
|
231
247
|
|
|
232
248
|
Two honesty flags, because a count you could not finish must never read as "it
|
|
@@ -70,10 +70,12 @@ module MailerToGo
|
|
|
70
70
|
@resolver = resolver
|
|
71
71
|
@term_class = term_class || Term
|
|
72
72
|
@spent = 0
|
|
73
|
-
@total =
|
|
73
|
+
@total = nil
|
|
74
74
|
@terms = []
|
|
75
75
|
@targets_without_spf = []
|
|
76
76
|
@duplicated_in_chain = []
|
|
77
|
+
# nil until we resolve the apex ourselves — "cannot say", not "no".
|
|
78
|
+
@apex_duplicated = nil
|
|
77
79
|
@resolved = true
|
|
78
80
|
@partial = false
|
|
79
81
|
@capped = false
|
|
@@ -100,7 +102,11 @@ module MailerToGo
|
|
|
100
102
|
seen_all ||= term.all?
|
|
101
103
|
end
|
|
102
104
|
|
|
103
|
-
|
|
105
|
+
# nil, not 0, when there is nothing to price. 0 is a legitimate total —
|
|
106
|
+
# `v=spf1 -all` costs exactly that — so using it for "no record" lets a
|
|
107
|
+
# caller that skipped #published? report "this record costs 0 lookups"
|
|
108
|
+
# about a domain with no record at all.
|
|
109
|
+
@total = published? ? @spent : nil
|
|
104
110
|
self
|
|
105
111
|
end
|
|
106
112
|
|
|
@@ -128,10 +134,20 @@ module MailerToGo
|
|
|
128
134
|
# the number is a floor — for a different reason.
|
|
129
135
|
def capped? = @capped
|
|
130
136
|
|
|
137
|
+
# Was there more than one v=spf1 record at the hostname itself? §4.5 makes
|
|
138
|
+
# that a permerror just as surely as duplicates inside the chain.
|
|
139
|
+
#
|
|
140
|
+
# nil means "cannot say" rather than "no": the record was handed to us, so
|
|
141
|
+
# we never looked at the apex (or DNS did not answer). Only a caller that
|
|
142
|
+
# let us resolve the apex gets a true/false here — the one that resolved it
|
|
143
|
+
# already knows, and should report from what it saw rather than ask us.
|
|
144
|
+
def apex_duplicated? = @apex_duplicated
|
|
145
|
+
|
|
131
146
|
# How many lookups are still available before the cap, or nil when we
|
|
132
|
-
# cannot say (a floor cannot answer "how much room is left"
|
|
147
|
+
# cannot say (a floor cannot answer "how much room is left", and neither
|
|
148
|
+
# can a record that does not exist).
|
|
133
149
|
def headroom
|
|
134
|
-
return nil if partial? || capped?
|
|
150
|
+
return nil if partial? || capped? || !published?
|
|
135
151
|
|
|
136
152
|
[LIMIT - @total.to_i, 0].max
|
|
137
153
|
end
|
|
@@ -150,9 +166,14 @@ module MailerToGo
|
|
|
150
166
|
|
|
151
167
|
records = Array(txts).map { |t| Record.normalize_txt(t) }.select { |t| Record.spf_record?(t) }
|
|
152
168
|
# §4.5 — two records at the apex permerror the evaluation just as surely
|
|
153
|
-
# as two anywhere else in the chain
|
|
154
|
-
#
|
|
155
|
-
|
|
169
|
+
# as two anywhere else in the chain. It is reported SEPARATELY from
|
|
170
|
+
# `duplicated_in_chain` even so, because the apex is not *in* the chain:
|
|
171
|
+
# it is the record being priced. Folding it in made the same domain audit
|
|
172
|
+
# differently depending on whether the caller resolved the apex or we
|
|
173
|
+
# did, and a caller that raises its own duplicate-record defect from the
|
|
174
|
+
# records it resolved would then raise it twice.
|
|
175
|
+
@apex_duplicated = records.size > 1
|
|
176
|
+
# We price the first, because there has to be something to read.
|
|
156
177
|
@record = records.first.to_s
|
|
157
178
|
end
|
|
158
179
|
|