dnscontrol/models/t_txt.go
Tom Limoncelli cbccbbeb8d
REFACTOR: Opinion: TXT records are one long string (#2631)
Co-authored-by: Costas Drogos <costas.drogos@gmail.com>
Co-authored-by: imlonghao <git@imlonghao.com>
Co-authored-by: Jeffrey Cafferata <jeffrey@jcid.nl>
Co-authored-by: Vincent Hagen <blackshadev@users.noreply.github.com>
2023-12-04 17:45:25 -05:00

93 lines
2.9 KiB
Go

package models
import (
"strings"
)
/*
Sadly many providers handle TXT records in strange and unexpeected
ways. DNSControl has to handle all of them. Over the years we've
tried many things. This explain the current state of the code.
DNSControl stores the TXT record target as a single string of any length.
Providers take care of any splitting, excaping, or quoting.
NOTE: Older versions of DNSControl stored the TXT record as
represented by the provider, which could be a single string, a series
of smaller strings, or a single string that is quoted/escaped. This
created tons of edge-cases and other distractions.
If a provider doesn't support certain charactors in a TXT record, use
the providers/$PROVIDER/auditrecords.go file to indicate this.
DNSControl uses this information to warn users of unsupporrted input,
and to skip related integration tests.
There are 2 ways to create a TXT record:
SetTargetTXT(): Create from a string.
SetTargetTXTs(): Create from an array of strings that need to be joined.
There are 2 ways to get the value (target) of a TXT record:
GetTargetTXTJoined(): Returns one big string
GetTargetTXTSegmented(): Returns an array 255-octet segments.
*/
// HasFormatIdenticalToTXT returns if a RecordConfig has a format which is
// identical to TXT, such as SPF. For more details, read
// https://tools.ietf.org/html/rfc4408#section-3.1.1
func (rc *RecordConfig) HasFormatIdenticalToTXT() bool {
return rc.Type == "TXT" || rc.Type == "SPF"
}
// SetTargetTXT sets the TXT fields when there is 1 string.
func (rc *RecordConfig) SetTargetTXT(s string) error {
if rc.Type == "" {
rc.Type = "TXT"
} else if !rc.HasFormatIdenticalToTXT() {
panic("assertion failed: SetTargetTXT called when .Type is not TXT or compatible type")
}
return rc.SetTarget(s)
}
// SetTargetTXTs sets the TXT fields when there are many strings. They are stored concatenated.
func (rc *RecordConfig) SetTargetTXTs(s []string) error {
return rc.SetTargetTXT(strings.Join(s, ""))
}
// GetTargetTXTJoined returns the TXT target as one string.
func (rc *RecordConfig) GetTargetTXTJoined() string {
return rc.target
}
// GetTargetTXTSegmented returns the TXT target as 255-octet segments, with the remainder in the last segment.
func (rc *RecordConfig) GetTargetTXTSegmented() []string {
return splitChunks(rc.target, 255)
}
// GetTargetTXTSegmentCount returns the number of 255-octet segments required to store TXT target.
func (rc *RecordConfig) GetTargetTXTSegmentCount() int {
total := len(rc.target)
segs := total / 255 // integer division, decimals are truncated
if (total % 255) > 0 {
return segs + 1
}
return segs
}
func splitChunks(buf string, lim int) []string {
if len(buf) == 0 {
return nil
}
var chunk string
chunks := make([]string, 0, len(buf)/lim+1)
for len(buf) >= lim {
chunk, buf = buf[:lim], buf[lim:]
chunks = append(chunks, chunk)
}
if len(buf) > 0 {
chunks = append(chunks, buf[:])
}
return chunks
}