1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

move txt encode/decode to txtutil

This commit is contained in:
Tom Limoncelli
2023-11-12 14:26:27 -05:00
parent 675a44619d
commit 6448fc5a8e
11 changed files with 41 additions and 240 deletions

View File

@ -1074,7 +1074,7 @@ func makeTests(t *testing.T) []*TestGroup {
// update the AuditRecords().
// Commented this one out. Nobody supports this or needs it.
tc("a 0-byte TXT", txt("foo0", "")),
//tc("a 0-byte TXT", txt("foo0", "")),
tc("a 254-byte TXT", txt("foo254", strings.Repeat("B", 254))),
tc("a 255-byte TXT", txt("foo255", strings.Repeat("C", 255))),
@ -1088,17 +1088,17 @@ func makeTests(t *testing.T) []*TestGroup {
tc("TXT with 1 backtick", txt("foobt", "blah`blah")),
tc("TXT with 1 double-quotes", txt("foodq", `quo"te`)),
tc("TXT with 2 double-quotes", txt("foodqs", `q"uo"te`)),
tc("TXT with 1 backslash", txt("fooosbs", `back\slash`)),
tc("TXT with 1 backslash", txt("fooosbs", `backs\lash`)),
tc("TXT interior ws", txt("foosp", "with spaces")),
tc("TXT trailing ws", txt("foows1", "with space at end ")),
//tc("Create a TXT/SPF", txt("foo", "v=spf1 ip4:99.99.99.99 -all")),
tc("Create a TXT/SPF", txt("foo", "v=spf1 ip4:99.99.99.99 -all")),
// This was added because Vultr syntax-checks TXT records with SPF contents.
//clear(),
// TODO(tlim): Re-add this when we fix the RFC1035 escaped-quotes issue.
tc("Create TXT with frequently escaped characters", txt("fooex", `!^.*$@#%^&()([][{}{<></:;-_=+\`)),
//tc("Create TXT with frequently escaped characters", txt("fooex", `!^.*$@#%^&()([][{}{<></:;-_=+\`)),
//clear(),
),

View File

@ -4,6 +4,7 @@ package models
import (
"fmt"
"os"
"strings"
"github.com/miekg/dns"
@ -97,6 +98,7 @@ func RRtoRC(rr dns.RR, origin string) (RecordConfig, error) {
case *dns.TLSA:
err = rc.SetTargetTLSA(v.Usage, v.Selector, v.MatchingType, v.Certificate)
case *dns.TXT:
fmt.Fprintf(os.Stdout, "DEBUG: RRtoRC TXT inboundv=%v\n", v.Txt)
err = rc.SetTargetTXTs(v.Txt)
default:
return *rc, fmt.Errorf("rrToRecord: Unimplemented zone record type=%s (%v)", rc.Type, rr)

View File

@ -4,9 +4,11 @@ import (
"encoding/json"
"fmt"
"log"
"os"
"sort"
"strings"
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
"github.com/jinzhu/copier"
"github.com/miekg/dns"
"github.com/miekg/dns/dnsutil"
@ -314,13 +316,18 @@ func (rc *RecordConfig) GetLabelFQDN() string {
// ToDiffable returns a string that is comparable by a differ.
// extraMaps: a list of maps that should be included in the comparison.
// NB(tlim): This will be deprecated when pkg/diff is replaced by pkg/diff2.
// Use // ToComparableNoTTL() instead.
// Use ToComparableNoTTL() instead.
func (rc *RecordConfig) ToDiffable(extraMaps ...map[string]string) string {
var content string
switch rc.Type {
case "SOA":
content = fmt.Sprintf("%s %v %d %d %d %d ttl=%d", rc.target, rc.SoaMbox, rc.SoaRefresh, rc.SoaRetry, rc.SoaExpire, rc.SoaMinttl, rc.TTL)
// SoaSerial is not used in comparison
case "TXT":
fmt.Fprintf(os.Stdout, "DEBUG: XXXXXXXXXXXXXXXX\n")
t := rc.GetTargetField()
te := txtutil.EncodeQuoted(t)
content = fmt.Sprintf("%v ttl=%d", te, rc.TTL)
default:
content = fmt.Sprintf("%v ttl=%d", rc.GetTargetCombined(), rc.TTL)
}
@ -352,6 +359,10 @@ func (rc *RecordConfig) ToDiffable(extraMaps ...map[string]string) string {
// pseudo-records like ANAME or R53_ALIAS
// This replaces ToDiff()
func (rc *RecordConfig) ToComparableNoTTL() string {
if rc.Type == "TXT" {
fmt.Fprintf(os.Stdout, "DEBUG: ToComNoTTL txts=%s q=%q\n", rc.target, rc.target)
return txtutil.EncodeQuoted(rc.target)
}
return rc.GetTargetCombined()
}

View File

@ -10,6 +10,8 @@ import (
"strings"
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
"github.com/miekg/dns"
)
@ -140,7 +142,10 @@ func (z *ZoneGenData) generateZoneFileHelper(w io.Writer) error {
// the remaining line
var target string
if typeStr == "TXT" {
target = txtToNative(rr.GetTargetTXTChunked255())
t := rr.GetTargetField()
target = txtutil.EncodeQuoted(t)
printer.Printf("DEBUG: pretty txt outbounds=%s\n", t)
printer.Printf("DEBUG: pretty txt encodedv=%v\n", target)
} else {
target = rr.GetTargetCombined()
}

View File

@ -1,6 +1,6 @@
//go:generate stringer -type=State
package gcloud
package txtutil
import (
"bytes"
@ -19,6 +19,14 @@ const (
StateWantSpaceOrQuote // expect open quote after `" `
)
func ParseQuoted(s string) (string, error) {
return txtDecode(s)
}
func EncodeQuoted(t string) string {
return txtEncode(ToChunks(t))
}
func isRemaining(s string, i, r int) bool {
return (len(s) - 1 - i) > r
}

View File

@ -1,4 +1,4 @@
package route53
package txtutil
import (
"strings"

View File

@ -12,6 +12,7 @@ import (
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/diff"
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
"github.com/StackExchange/dnscontrol/v4/providers"
gauth "golang.org/x/oauth2/google"
gdns "google.golang.org/api/dns/v1"
@ -310,10 +311,10 @@ func (g *gcloudProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, exis
//chunks := txtutil.ToChunks(r.GetTargetField())
//printer.Printf("DEBUG: gcloud txt chunks=%+v\n", chunks)
//newRRs.Rrdatas = append(newRRs.Rrdatas, chunks[0:]...)
t := r.GetTargetTXTChunked255()
t := r.GetTargetField()
printer.Printf("DEBUG: gcloud outboundv=%v\n", t)
//tc := txtutil.RFC1035ChunkedAndQuoted(t)
tc := txtEncode(t)
tc := txtutil.EncodeQuoted(t)
printer.Printf("DEBUG: gcloud encodedv=%v\n", tc)
newRRs.Rrdatas = append(newRRs.Rrdatas, tc)
} else {
@ -421,7 +422,7 @@ func nativeToRecord(set *gdns.ResourceRecordSet, rec, origin string) (*models.Re
//t := r
//te := models.ParseQuotedTxt(rec)
t := rec
td, err := txtDecode(t)
td, err := txtutil.ParseQuoted(t)
if err != nil {
return nil, fmt.Errorf("gcloud TXT parse error: %w", err)
}

View File

@ -1,81 +0,0 @@
package gcloud
import (
"strings"
"testing"
)
func r(s string, c int) string { return strings.Repeat(s, c) }
func TestTxtDecode(t *testing.T) {
tests := []struct {
data string
expected []string
}{
{``, []string{``}},
{`""`, []string{``}},
{`foo`, []string{`foo`}},
{`"foo"`, []string{`foo`}},
{`"foo bar"`, []string{`foo bar`}},
{`foo bar`, []string{`foo`, `bar`}},
{`"aaa" "bbb"`, []string{`aaa`, `bbb`}},
{`"a\"a" "bbb"`, []string{`a"a`, `bbb`}},
// Seen in live traffic:
{"\"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\"",
[]string{r("B", 254)}},
{"\"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\"",
[]string{r("C", 255)}},
{"\"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD\" \"D\"",
[]string{r("D", 255), "D"}},
{"\"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\" \"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\"",
[]string{r("E", 255), r("E", 255)}},
{"\"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\" \"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\" \"F\"",
[]string{r("F", 255), r("F", 255), "F"}},
{"\"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\" \"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\" \"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\"",
[]string{r("G", 255), r("G", 255), r("G", 255)}},
{"\"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH\" \"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH\" \"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH\" \"H\"",
[]string{r("H", 255), r("H", 255), r("H", 255), "H"}},
{"\"quo'te\"", []string{`quo'te`}},
{"\"blah`blah\"", []string{"blah`blah"}},
{"\"quo\\\"te\"", []string{`quo"te`}},
{"\"q\\\"uo\\\"te\"", []string{`q"uo"te`}},
{"\"backs\\\\lash\"", []string{`backs\lash`}},
}
for i, test := range tests {
got, err := txtDecode(test.data)
if err != nil {
t.Error(err)
}
want := strings.Join(test.expected, "")
if got != want {
t.Errorf("%v: expected TxtStrings=(%q) got (%q)", i, want, got)
}
}
}
func TestTxtEncode(t *testing.T) {
tests := []struct {
data []string
expected string
}{
{[]string{``}, `""`},
{[]string{`foo`}, `"foo"`},
{[]string{`aaa`, `bbb`}, `"aaa" "bbb"`},
{[]string{`ccc`, `ddd`, `eee`}, `"ccc" "ddd" "eee"`},
{[]string{`a"a`, `bbb`}, `"a\"a" "bbb"`},
{[]string{`quo'te`}, "\"quo'te\""},
{[]string{"blah`blah"}, "\"blah`blah\""},
{[]string{`quo"te`}, "\"quo\\\"te\""},
{[]string{`q"uo"te`}, "\"q\\\"uo\\\"te\""},
{[]string{`backs\lash`}, "\"backs\\\\lash\""},
}
for i, test := range tests {
got := txtEncode(test.data)
want := test.expected
if got != want {
t.Errorf("%v: expected TxtStrings=v(%v) got (%v)", i, want, got)
}
}
}

View File

@ -14,6 +14,7 @@ import (
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
"github.com/StackExchange/dnscontrol/v4/providers"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
@ -356,7 +357,7 @@ func (r *route53Provider) GetZoneRecordsCorrections(dc *models.DomainConfig, exi
// t := `"` + strings.Join(ts, `" "`) + `"`
// printer.Printf("DEBUG: txt outboundv=%v\n", t)
t := txtEncode(r.GetTargetTXTChunked255())
t := txtutil.EncodeQuoted(r.GetTargetField())
//printer.Printf("XXXXXXXXX %v\n", t)
rr = r53Types.ResourceRecord{
@ -523,7 +524,7 @@ func nativeToRecords(set r53Types.ResourceRecordSet, origin string) ([]*models.R
//err = rc.SetTargetTXTs(dt)
var t string
t, err = txtDecode(val)
t, err = txtutil.ParseQuoted(val)
if err == nil {
err = rc.SetTargetTXT(t)
}

View File

@ -1,28 +0,0 @@
// Code generated by "stringer -type=State"; DO NOT EDIT.
package route53
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[StateStart-0]
_ = x[StateQuoted-1]
_ = x[StateBackslash-2]
_ = x[StateQuotedBackslash-3]
_ = x[StateWantSpace-4]
_ = x[StateWantSpaceOrQuote-5]
}
const _State_name = "StateStartStateQuotedStateBackslashStateQuotedBackslashStateWantSpaceStateWantSpaceOrQuote"
var _State_index = [...]uint8{0, 10, 21, 35, 55, 69, 90}
func (i State) String() string {
if i < 0 || i >= State(len(_State_index)-1) {
return "State(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _State_name[_State_index[i]:_State_index[i+1]]
}

View File

@ -1,118 +0,0 @@
//go:generate stringer -type=State
package route53
import (
"bytes"
"fmt"
"strings"
)
type State int
const (
StateStart State = iota // Normal text
StateQuoted // Quoted text
StateBackslash // last char was backslash
StateQuotedBackslash // last char was backlash in a quoted string
StateWantSpace // expect space after closing quote
StateWantSpaceOrQuote // expect open quote after `" `
)
func isRemaining(s string, i, r int) bool {
return (len(s) - 1 - i) > r
}
// txtDecode decodes TXT strings received from ROUTE53.
func txtDecode(s string) (string, error) {
// Parse according to RFC1035 zonefile specifications.
// "foo" -> one string: `foo``
// "foo" "bar" -> two strings: `foo` and `bar`
// quotes and backslashes are escaped using \
//printer.Printf("DEBUG: route53 txt inboundv=%v\n", s)
b := &bytes.Buffer{}
state := StateStart
for i, c := range s {
//printer.Printf("DEBUG: state=%v rune=%v\n", state, string(c))
switch state {
case StateStart:
if c == '"' {
state = StateQuoted
} else if c == ' ' {
state = StateQuoted
} else if c == '\\' {
if isRemaining(s, i, 1) {
state = StateBackslash
} else {
return "", fmt.Errorf("txtDecode string ends with backslash q(%q)", s)
}
} else {
b.WriteRune(c)
}
case StateBackslash:
b.WriteRune(c)
state = StateStart
case StateQuoted:
if c == '\\' {
if isRemaining(s, i, 1) {
state = StateQuotedBackslash
} else {
return "", fmt.Errorf("txtDecode quoted string ends with backslash q(%q)", s)
}
} else if c == '"' {
state = StateWantSpace
} else {
b.WriteRune(c)
}
case StateQuotedBackslash:
b.WriteRune(c)
state = StateQuoted
case StateWantSpace:
if c == ' ' {
state = StateWantSpaceOrQuote
} else {
return "", fmt.Errorf("txtDecode expected whitespace after close quote q(%q)", s)
}
case StateWantSpaceOrQuote:
if c == ' ' {
state = StateWantSpaceOrQuote
} else if c == '"' {
state = StateQuoted
} else {
state = StateStart
b.WriteRune(c)
}
}
}
r := b.String()
//printer.Printf("DEBUG: route53 txt decodedv=%v\n", r)
return r, nil
}
// txtEncode encodes TXT strings as expected by ROUTE53.
func txtEncode(ts []string) string {
//printer.Printf("DEBUG: route53 txt outboundv=%v\n", ts)
for i := range ts {
ts[i] = strings.ReplaceAll(ts[i], `\`, `\\`)
ts[i] = strings.ReplaceAll(ts[i], `"`, `\"`)
}
t := `"` + strings.Join(ts, `" "`) + `"`
//printer.Printf("DEBUG: route53 txt encodedv=%v\n", t)
return t
}