add stutter checking

This commit is contained in:
Thomas Limoncelli 2025-12-17 17:40:00 -05:00
parent c87451ccda
commit cd73784860
No known key found for this signature in database
4 changed files with 95 additions and 13 deletions

View file

@ -118,7 +118,7 @@ func TestParsedFiles(t *testing.T) {
} else {
zoneFile = filepath.Join(testDir, testName, dc.Name+".zone")
}
fmt.Printf("DEBUG: zonefile = %q\n", zoneFile)
//fmt.Printf("DEBUG: zonefile = %q\n", zoneFile)
expectedZone, err := os.ReadFile(zoneFile)
if err != nil {
continue

View file

@ -18,16 +18,20 @@ func ImportRawRecords(domains []*models.DomainConfig) error {
if err != nil {
return err
}
// TODO(tlim): Check if rec.Name might be a typo of dc.Name. But not if meta["skip_fqdn_check"]=="true"
// See "validate.go"
/*
}
if label == domain || strings.HasSuffix(label, "."+domain) {
if m := meta["skip_fqdn_check"]; m != "true" {
return errors.New(errorRepeat(label, domain))
}
*/
if rec.Metadata["skip_fqdn_check"] != "true" && stutters(rec.Name, dc.Name) {
var shortname string
if rec.Name == dc.Name {
shortname = "@"
} else {
shortname = strings.TrimSuffix(rec.Name, "."+dc.Name)
}
return fmt.Errorf(
"The name %q is an error (repeats the domain). Maybe instead of %q you intended %q? If not add DISABLE_REPEATED_DOMAIN_CHECK to this record to disable this check",
rec.NameFQDNRaw,
rec.NameRaw,
shortname,
)
}
// Free memeory:
clear(rawRec.Args)
@ -41,6 +45,16 @@ func ImportRawRecords(domains []*models.DomainConfig) error {
return nil
}
func stutters(name, domain string) bool {
if name == "@" {
return false
}
if name == domain || strings.HasSuffix(name, "."+domain) {
return true
}
return false
}
// NewRecordConfigFromRaw creates a new RecordConfig from the raw ([]any) args,
// usually from the parsed dnsconfig.js file, but also useful when a provider
// returns the fields of a record as individual values.

View file

@ -0,0 +1,59 @@
package rtypecontrol
import "testing"
func Test_stutters(t *testing.T) {
tests := []struct {
name string
rName string
want bool
}{
{
name: "@ symbol should not stutter",
rName: "@",
want: false,
},
{
name: "exact domain match should stutter",
rName: "example.com",
want: true,
},
{
name: "subdomain with dot prefix should stutter",
rName: "www.example.com",
want: true,
},
{
name: "simple subdomain should not stutter",
rName: "www",
want: false,
},
{
name: "partial match without dot should not stutter",
rName: "testexample.com",
want: false,
},
{
name: "empty name should not stutter",
rName: "",
want: false,
},
{
name: "nested subdomain should stutter",
rName: "api.staging.example.com",
want: true,
},
{
name: "different domain should not stutter",
rName: "example.org",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := stutters(tt.rName, "example.com"); got != tt.want {
t.Errorf("stutters(%q, %q) = %v, want %v", tt.rName, "example.com", got, tt.want)
}
})
}
}

View file

@ -8,6 +8,11 @@ import (
"github.com/StackExchange/dnscontrol/v4/pkg/domaintags"
)
// This code defines many variables to make the logic easier to read. The Go optimizer
// should eliminate any performance impact.
// We could probably fold some of the logic together, but it would be harder to read.
// It's difficult enough to understand it as-is, so clarity is preferred.
// setRecordNames uses n to update the .Name* fields. If the name is a FQDN
// (ends with a "."), it will be handled accordingly. However if it does not
// match the domain name, no error is returned but rec.Name* fields will end
@ -100,8 +105,12 @@ func setRecordNamesNonExtend(rec *models.RecordConfig, dcn *domaintags.DomainNam
}
func setRecordNamesExtend(rec *models.RecordConfig, dcn *domaintags.DomainNameVarieties, n string) error {
// NB(tlim): What's important to remember is that the domain is the parent D(), not D_EXTEND().
// That is... dcn.NameASCII, not rec.SubDomain+dcn.NameASCII.
// NB(tlim): When a record has a subdomain "foo" and domain "example.com", a
// record such as "www" is added as "www.foo" (short name) or
// "www.foo.example.com" (FQDN name).
// When generating the shortname, we are truncating the "D()" name, not the
// D_EXTEND() name. That is... dcn.NameASCII, not
// rec.SubDomain+dcn.NameASCII.
nRaw := n
nASCII := domaintags.EfficientToASCII(n)