fix
This commit is contained in:
4 files changed
+141
-1
No files matched your search
@@ -55,7 +55,9 @@ func normalizeName(name string) string {
|
||||
// normalizeValue canonicalizes a single RR value for comparison.
|
||||
func normalizeValue(t RecordType, content string) string {
|
||||
if t == TXT {
|
||||
return content // byte-exact — case and whitespace are significant (DKIM/SPF/DMARC)
|
||||
// byte-exact — case and whitespace are significant (DKIM/SPF/DMARC);
|
||||
// only the presentation quoting/chunking is stripped.
|
||||
return TXTPayload(content)
|
||||
}
|
||||
c := strings.Join(strings.Fields(content), " ") // collapse whitespace
|
||||
switch t {
|
||||
@@ -95,3 +97,47 @@ func (r Record) Equal(o Record) bool {
|
||||
}
|
||||
return slices.Equal(r.NormalizedValues(), o.NormalizedValues())
|
||||
}
|
||||
|
||||
// TXTPayload returns the logical content of a TXT value. A value written as
|
||||
// one or more quoted strings ("a" "b") is the concatenation of their
|
||||
// contents; an unquoted value is returned as is. This makes a template value
|
||||
// without quotes comparable with the quoted form providers store.
|
||||
func TXTPayload(v string) string {
|
||||
t := strings.TrimSpace(v)
|
||||
if !strings.HasPrefix(t, `"`) {
|
||||
return v
|
||||
}
|
||||
var b strings.Builder
|
||||
inQ := false
|
||||
for i := 0; i < len(t); i++ {
|
||||
c := t[i]
|
||||
switch {
|
||||
case inQ && c == '\\' && i+1 < len(t):
|
||||
i++
|
||||
b.WriteByte(t[i])
|
||||
case c == '"':
|
||||
inQ = !inQ
|
||||
case inQ:
|
||||
b.WriteByte(c)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// QuoteTXT renders a TXT value in the quoted form providers require: payload
|
||||
// split into <=255-byte strings, each quoted. Already-quoted values pass
|
||||
// through unchanged.
|
||||
func QuoteTXT(v string) string {
|
||||
if strings.HasPrefix(strings.TrimSpace(v), `"`) {
|
||||
return v
|
||||
}
|
||||
const max = 255
|
||||
esc := strings.NewReplacer(`\`, `\\`, `"`, `\"`)
|
||||
var parts []string
|
||||
for len(v) > max {
|
||||
parts = append(parts, `"`+esc.Replace(v[:max])+`"`)
|
||||
v = v[max:]
|
||||
}
|
||||
parts = append(parts, `"`+esc.Replace(v)+`"`)
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestQuoteTXTAndPayloadRoundTrip(t *testing.T) {
|
||||
long := "v=DKIM1; p=" + strings.Repeat("A", 400)
|
||||
for _, v := range []string{"v=spf1 ~all", long, `a"b\c`} {
|
||||
q := QuoteTXT(v)
|
||||
if !strings.HasPrefix(q, `"`) {
|
||||
t.Fatalf("not quoted: %q", q)
|
||||
}
|
||||
if got := TXTPayload(q); got != v {
|
||||
t.Fatalf("roundtrip: got %q want %q", got, v)
|
||||
}
|
||||
if TXTPayload(v) != v && !strings.HasPrefix(v, `"`) {
|
||||
t.Fatalf("unquoted payload changed")
|
||||
}
|
||||
}
|
||||
if QuoteTXT(`"x"`) != `"x"` {
|
||||
t.Fatal("quoted must pass through")
|
||||
}
|
||||
a := Record{Type: TXT, Values: []string{"v=DKIM1; k=rsa"}}
|
||||
b := Record{Type: TXT, Values: []string{`"v=DKIM1; k=rsa"`}}
|
||||
if !a.Equal(b) {
|
||||
t.Fatal("quoted and unquoted must compare equal")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user