package config import ( "errors" "fmt" "strings" "golang.org/x/net/publicsuffix" ) // DNSNameType indicates whether a DNS name is an apex domain or a hostname. type DNSNameType int const ( // DNSNameTypeDomain indicates the name is an apex (eTLD+1) domain. DNSNameTypeDomain DNSNameType = iota // DNSNameTypeHostname indicates the name is a subdomain/hostname. DNSNameTypeHostname ) // ErrEmptyDNSName is returned when an empty string is passed to ClassifyDNSName. var ErrEmptyDNSName = errors.New("empty DNS name") // String returns the string representation of a DNSNameType. func (t DNSNameType) String() string { switch t { case DNSNameTypeDomain: return "domain" case DNSNameTypeHostname: return "hostname" default: return "unknown" } } // ClassifyDNSName determines whether a DNS name is an apex domain or a // hostname (subdomain) using the Public Suffix List. It returns an error // if the input is empty or is itself a public suffix (e.g. "co.uk"). func ClassifyDNSName(name string) (DNSNameType, error) { name = strings.ToLower(strings.TrimSuffix(strings.TrimSpace(name), ".")) if name == "" { return 0, ErrEmptyDNSName } etld1, err := publicsuffix.EffectiveTLDPlusOne(name) if err != nil { return 0, fmt.Errorf("invalid DNS name %q: %w", name, err) } if name == etld1 { return DNSNameTypeDomain, nil } return DNSNameTypeHostname, nil } // ClassifyTargets splits a list of DNS names into apex domains and // hostnames using the Public Suffix List. It returns an error if any // name cannot be classified. func ClassifyTargets(targets []string) ([]string, []string, error) { var domains, hostnames []string for _, t := range targets { normalized := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(t), ".")) if normalized == "" { continue } typ, classErr := ClassifyDNSName(normalized) if classErr != nil { return nil, nil, classErr } switch typ { case DNSNameTypeDomain: domains = append(domains, normalized) case DNSNameTypeHostname: hostnames = append(hostnames, normalized) } } return domains, hostnames, nil }