pull/18/head
Hugo Landau 10 years ago
parent d8c033a4df
commit 1896399ba8

@ -1,16 +1,14 @@
package backend
import "github.com/golang/groupcache/lru"
import "github.com/miekg/dns"
import "github.com/hlandau/degoutils/log"
import "fmt"
import "strings"
import "net"
import "github.com/hlandau/ncdns/namecoin"
import "github.com/golang/groupcache/lru"
import "github.com/hlandau/madns/merr"
import "github.com/hlandau/ncdns/namecoin"
import "github.com/hlandau/ncdns/util"
import "github.com/hlandau/ncdns/ncdomain"
import "sync"
import "fmt"
import "net"
// Provides an abstract zone file for the Namecoin .bit TLD.
type Backend struct {
@ -21,9 +19,7 @@ type Backend struct {
cfg Config
}
const (
defaultMaxEntries = 100
)
const defaultMaxEntries = 100
// Backend configuration.
type Config struct {
@ -65,108 +61,21 @@ func New(cfg *Config) (backend *Backend, err error) {
b.cache.MaxEntries = defaultMaxEntries
}
if b.cfg.FakeNames == nil {
b.cfg.FakeNames = map[string]string{}
}
backend = b
return
}
// Keep domains in parsed format.
type domain struct {
ncv *ncdomain.Value
}
func toNamecoinName(basename string) (string, error) {
return "d/" + basename, nil
}
func (b *Backend) getNamecoinEntry(name string) (*domain, error) {
d := b.getNamecoinEntryCache(name)
if d != nil {
return d, nil
}
d, err := b.getNamecoinEntryLL(name)
if err != nil {
return nil, err
}
b.addNamecoinEntryToCache(name, d)
return d, nil
}
func (b *Backend) getNamecoinEntryCache(name string) *domain {
b.cacheMutex.Lock()
defer b.cacheMutex.Unlock()
if dd, ok := b.cache.Get(name); ok {
d := dd.(*domain)
return d
}
return nil
}
func (b *Backend) addNamecoinEntryToCache(name string, d *domain) {
b.cacheMutex.Lock()
defer b.cacheMutex.Unlock()
b.cache.Add(name, d)
}
func (b *Backend) resolveName(name string) (jsonValue string, err error) {
if fv, ok := b.cfg.FakeNames[name]; ok {
if fv == "NX" {
return "", merr.ErrNoSuchDomain
}
return fv, nil
}
v, err := b.nc.Query(name)
if err != nil {
return "", err
}
return v, nil
}
func (b *Backend) getNamecoinEntryLL(name string) (*domain, error) {
v, err := b.resolveName(name)
if err != nil {
return nil, err
}
log.Info("namecoin query (", name, ") succeeded: ", v)
d, err := b.jsonToDomain(name, v)
if err != nil {
log.Infoe(err, "cannot convert JSON to domain")
return nil, err
}
return d, nil
}
func (b *Backend) jsonToDomain(name, jsonValue string) (*domain, error) {
d := &domain{}
v, err := ncdomain.ParseValue(name, jsonValue, b.resolveExtraName)
if err != nil {
return nil, err
}
d.ncv = v
return d, nil
}
func (b *Backend) resolveExtraName(name string) (jsonValue string, err error) {
return b.resolveName(name)
// Do low-level queries against an abstract zone file. This is the per-query
// entrypoint from madns.
func (b *Backend) Lookup(qname string) (rrs []dns.RR, err error) {
btx := &btx{}
btx.b = b
btx.qname = qname
return btx.Do()
}
// Things to keep track of while processing a query.
type btx struct {
b *Backend
qname string
@ -174,73 +83,47 @@ type btx struct {
subname, basename, rootname string
}
func (tx *btx) determineDomain() (subname, basename, rootname string, err error) {
qname := tx.qname
qname = strings.TrimRight(qname, ".")
parts := strings.Split(qname, ".")
if len(parts) < 2 {
if parts[0] != "bit" {
err = merr.ErrNotInZone
return
}
rootname = parts[0]
return
}
for i := len(parts) - 1; i >= 0; i-- {
v := parts[i]
// scanning for rootname
if v == "bit" {
if i == 0 {
// i is already zero, so we have something like bit.x.y.z.
rootname = qname
return
}
rootname = strings.Join(parts[i:len(parts)], ".")
basename = parts[i-1]
subname = strings.Join(parts[0:i-1], ".")
return
}
}
err = merr.ErrNotInZone
return
}
func (tx *btx) Do() (rrs []dns.RR, err error) {
// Split the domain up. 'rootname' is the TLD and everything after it,
// basename is the name directly before that, and subname is every name
// before that. So "a.b.example.bit.suffix.xyz." would have a subname
// of "a.b", a basename of "example" and a rootname of "bit.suffix.xyz".
tx.subname, tx.basename, tx.rootname, err = tx.determineDomain()
if err != nil {
log.Infoe(err, "couldn't determine domain")
// We get an error if '.bit.' does not appear anywhere. In that case
// we're not authoritative for the query in question and error out.
return
}
log.Info("domain: sub=", tx.subname, " basename=", tx.basename, " rootname=", tx.rootname)
if tx.rootname == "" {
// REFUSED
return nil, merr.ErrNotInZone
}
// If subname and basename are "", this means the query was for a root name
// such as "bit." or "bit.suffix.xyz." directly. Serve SOA, NS records, etc.
// as requested.
if tx.subname == "" && tx.basename == "" {
return tx.doRootDomain()
}
// Where ncdns has not been configured with a hostname to identify itself by,
// it generates one under a special meta domain "x--nmc". This domain is not
// a valid Namecoin domain name, so it does not confict with the Namecoin
// domain name namespace.
if tx.basename == "x--nmc" && tx.b.cfg.SelfName == "" {
return tx.doMetaDomain()
}
// If we have reached this point the query must be a normal user query.
rrs, err = tx.doUserDomain()
log.Info("USER RECORDS YIELDED:")
for _, rr := range rrs {
log.Info(" ", rr.String())
}
return
}
func (tx *btx) determineDomain() (subname, basename, rootname string, err error) {
return util.SplitDomainByFloatingAnchor(tx.qname, "bit")
}
func (tx *btx) doRootDomain() (rrs []dns.RR, err error) {
nsname := tx.b.cfg.SelfName
if nsname == "" {
@ -304,27 +187,110 @@ func (tx *btx) doMetaDomain() (rrs []dns.RR, err error) {
}
func (tx *btx) doUserDomain() (rrs []dns.RR, err error) {
ncname, err := toNamecoinName(tx.basename)
ncname, err := util.BasenameToNamecoinKey(tx.basename)
if err != nil {
log.Infoe(err, "cannot determine namecoin name")
return
}
d, err := tx.b.getNamecoinEntry(ncname)
if err != nil {
log.Infoe(err, "cannot get namecoin entry")
return nil, err
}
rrs, err = tx.doUnderDomain(d)
if err != nil {
log.Infoe(err, "cannot process namecoin entry under domain")
return nil, err
}
return rrs, nil
}
// Keep domains in parsed format.
type domain struct {
ncv *ncdomain.Value
}
func (b *Backend) getNamecoinEntry(name string) (*domain, error) {
d := b.getNamecoinEntryCache(name)
if d != nil {
return d, nil
}
d, err := b.getNamecoinEntryLL(name)
if err != nil {
return nil, err
}
b.addNamecoinEntryToCache(name, d)
return d, nil
}
func (b *Backend) getNamecoinEntryCache(name string) *domain {
b.cacheMutex.Lock()
defer b.cacheMutex.Unlock()
if dd, ok := b.cache.Get(name); ok {
d := dd.(*domain)
return d
}
return nil
}
func (b *Backend) addNamecoinEntryToCache(name string, d *domain) {
b.cacheMutex.Lock()
defer b.cacheMutex.Unlock()
b.cache.Add(name, d)
}
func (b *Backend) getNamecoinEntryLL(name string) (*domain, error) {
v, err := b.resolveName(name)
if err != nil {
return nil, err
}
d, err := b.jsonToDomain(name, v)
if err != nil {
return nil, err
}
return d, nil
}
func (b *Backend) resolveName(name string) (jsonValue string, err error) {
if fv, ok := b.cfg.FakeNames[name]; ok {
if fv == "NX" {
return "", merr.ErrNoSuchDomain
}
return fv, nil
}
v, err := b.nc.Query(name)
if err != nil {
return "", err
}
return v, nil
}
func (b *Backend) jsonToDomain(name, jsonValue string) (*domain, error) {
d := &domain{}
v, err := ncdomain.ParseValue(name, jsonValue, b.resolveExtraName)
if err != nil {
return nil, err
}
d.ncv = v
return d, nil
}
func (b *Backend) resolveExtraName(name string) (jsonValue string, err error) {
return b.resolveName(name)
}
func (tx *btx) doUnderDomain(d *domain) (rrs []dns.RR, err error) {
rrs, err = tx.addAnswersUnderNCValue(d.ncv, tx.subname)
if err == merr.ErrNoResults {
@ -340,15 +306,9 @@ func (tx *btx) addAnswersUnderNCValue(rncv *ncdomain.Value, subname string) (rrs
return
}
log.Info("ncv actual: ", sn)
return tx.addAnswersUnderNCValueActual(ncv, sn)
}
/*func hasNS(ncv *ncdomain.Value) bool {
nss, err := ncv.GetNSs()
return err == nil && len(nss) > 0
}*/
func (tx *btx) findNCValue(ncv *ncdomain.Value, subname string, shortCircuitFunc func(curNCV *ncdomain.Value) bool) (xncv *ncdomain.Value, sn string, err error) {
return tx._findNCValue(ncv, subname, "", 0, shortCircuitFunc)
}
@ -381,7 +341,7 @@ func (tx *btx) _findNCValue(ncv *ncdomain.Value, isubname, subname string, depth
}
func (tx *btx) addAnswersUnderNCValueActual(ncv *ncdomain.Value, sn string) (rrs []dns.RR, err error) {
rrs, err = ncv.RRs(nil, dns.Fqdn(tx.qname), dns.Fqdn(tx.basename+"."+tx.rootname)) //convertAt(nil, dns.Fqdn(tx.qname), ncv)
rrs, err = ncv.RRs(nil, dns.Fqdn(tx.qname), dns.Fqdn(tx.basename+"."+tx.rootname))
return
}
@ -395,12 +355,4 @@ func (tx *btx) addAnswersUnderNCValueActual(ncv *ncdomain.Value, sn string) (rrs
// f[b]("a", "b.c.d.e.f.g.zzz.bit")
// f[a]("", "a.b.c.d.e.f.g.zzz.bit")
// Do low-level queries against an abstract zone file.
func (b *Backend) Lookup(qname string) (rrs []dns.RR, err error) {
btx := &btx{}
btx.b = b
btx.qname = qname
return btx.Do()
}
// © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later

@ -6,13 +6,12 @@ import "fmt"
import "github.com/miekg/dns"
import "encoding/base64"
import "encoding/hex"
import "regexp"
import "net/mail"
import "github.com/hlandau/ncdns/util"
import "strings"
const depthLimit = 16
const mergeDepthLimit = 4
const defaultTTL = 600
// Note: Name values in Value (e.g. those in Alias and Target, Services, MXs,
// etc.) are not necessarily fully qualified and must be fully qualified before
@ -94,7 +93,7 @@ func (v *Value) appendIPs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, er
Name: suffix,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 600,
Ttl: defaultTTL,
},
A: ip,
})
@ -110,7 +109,7 @@ func (v *Value) appendIP6s(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, e
Name: suffix,
Rrtype: dns.TypeAAAA,
Class: dns.ClassINET,
Ttl: 600,
Ttl: defaultTTL,
},
AAAA: ip,
})
@ -131,7 +130,7 @@ func (v *Value) appendNSs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, er
Name: suffix,
Rrtype: dns.TypeNS,
Class: dns.ClassINET,
Ttl: 600,
Ttl: defaultTTL,
},
Ns: qn,
})
@ -147,7 +146,7 @@ func (v *Value) appendTXTs(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, e
Name: suffix,
Rrtype: dns.TypeTXT,
Class: dns.ClassINET,
Ttl: 600,
Ttl: defaultTTL,
},
Txt: txt,
})
@ -199,7 +198,7 @@ func (v *Value) appendAlias(out []dns.RR, suffix, apexSuffix string) ([]dns.RR,
Name: suffix,
Rrtype: dns.TypeCNAME,
Class: dns.ClassINET,
Ttl: 600,
Ttl: defaultTTL,
},
Target: qn,
})
@ -219,7 +218,7 @@ func (v *Value) appendTranslate(out []dns.RR, suffix, apexSuffix string) ([]dns.
Name: suffix,
Rrtype: dns.TypeDNAME,
Class: dns.ClassINET,
Ttl: 600,
Ttl: defaultTTL,
},
Target: qn,
})
@ -235,7 +234,7 @@ func (v *Value) RRsRecursive(out []dns.RR, suffix, apexSuffix string) ([]dns.RR,
}
for mk, mv := range v.Map {
if !validateLabel(mk) && mk != "" && mk != "*" {
if !util.ValidateLabel(mk) && mk != "" && mk != "*" {
continue
}
@ -390,7 +389,7 @@ func (v *Value) qualifyIntl(name, suffix, apexSuffix string) string {
func (v *Value) qualify(name, suffix, apexSuffix string) (string, bool) {
s := v.qualifyIntl(name, suffix, apexSuffix)
if !validateHostName(s) {
if !util.ValidateHostName(s) {
return "", false
}
@ -630,7 +629,7 @@ func (rv *rawValue) parseHostmaster(v *Value) error {
}
if s, ok := rv.Hostmaster.(string); ok {
if !validateEmail(s) {
if !util.ValidateEmail(s) {
return fmt.Errorf("malformed e. mail address in email field")
}
@ -682,7 +681,7 @@ func (rv *rawValue) parseDS(v *Value) error {
a4h := hex.EncodeToString(a4b)
v.DS = append(v.DS, &dns.DS{
Hdr: dns.RR_Header{Rrtype: dns.TypeDS, Class: dns.ClassINET, Ttl: 600},
Hdr: dns.RR_Header{Rrtype: dns.TypeDS, Class: dns.ClassINET, Ttl: defaultTTL},
KeyTag: uint16(a1),
Algorithm: uint8(a2),
DigestType: uint8(a3),
@ -753,7 +752,8 @@ func (rv *rawValue) parseTLSA(v *Value) error {
name := "_" + ports + "._" + transport
v.TLSA = append(v.TLSA, &dns.TLSA{
Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeTLSA, Class: dns.ClassINET, Ttl: 600},
Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeTLSA, Class: dns.ClassINET,
Ttl: defaultTTL},
Usage: uint8(a1),
Selector: uint8(a2),
MatchingType: uint8(a3),
@ -865,7 +865,7 @@ func (rv *rawValue) parseSingleMX(s interface{}, v *Value, relname string) error
}
v.MX = append(v.MX, &dns.MX{
Hdr: dns.RR_Header{Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 600},
Hdr: dns.RR_Header{Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: defaultTTL},
Preference: uint16(prio),
Mx: hostname,
})
@ -910,12 +910,12 @@ func (rv *rawValue) parseSingleService(svc interface{}, v *Value, relname string
}
appProtoName, ok := svca[0].(string)
if !ok || !validateServiceName(appProtoName) {
if !ok || !util.ValidateServiceName(appProtoName) {
return fmt.Errorf("malformed service value")
}
transportProtoName, ok := svca[1].(string)
if !ok || !validateServiceName(transportProtoName) {
if !ok || !util.ValidateServiceName(transportProtoName) {
return fmt.Errorf("malformed service value")
}
@ -947,7 +947,7 @@ func (rv *rawValue) parseSingleService(svc interface{}, v *Value, relname string
Name: sname,
Rrtype: dns.TypeSRV,
Class: dns.ClassINET,
Ttl: 600,
Ttl: defaultTTL,
},
Priority: uint16(priority),
Weight: uint16(weight),
@ -1038,33 +1038,3 @@ func (v *Value) moveEmptyMapItems() error {
}
return nil
}
// Validation functions
// This is used to validate NS records, targets in SRV records, etc. In these cases
// an IP address is not allowed. Therefore this regex must exclude all-numeric domain names.
// This is done by requiring the final part to start with an alphabetic character.
var re_hostName = regexp.MustCompilePOSIX(`^(([a-z0-9_][a-z0-9_-]{0,62}\.)*[a-z_][a-z0-9_-]{0,62}\.?|\.)$`)
var re_serviceName = regexp.MustCompilePOSIX(`^[a-z_][a-z0-9_-]*$`)
var re_label = regexp.MustCompilePOSIX(`^[a-z0-9_][a-z0-9_-]*$`)
func validateHostName(name string) bool {
name = dns.Fqdn(name)
return len(name) <= 255 && re_hostName.MatchString(name)
}
func validateServiceName(name string) bool {
return len(name) < 63 && re_serviceName.MatchString(name)
}
func validateLabel(name string) bool {
return len(name) <= 63 && re_label.MatchString(name)
}
func validateEmail(email string) bool {
addr, err := mail.ParseAddress(email)
if addr == nil || err != nil {
return false
}
return addr.Name == ""
}

@ -6,8 +6,6 @@ import "github.com/hlandau/ncdns/backend"
import "github.com/miekg/dns"
import "os"
import "fmt"
import "os/signal"
import "syscall"
import "path/filepath"
const version = "1.0"
@ -68,23 +66,17 @@ func NewServer(cfg *ServerConfig) (s *Server, err error) {
// key setup
if cfg.PublicKey != "" {
ksk, kskPrivate, err := s.loadKey(cfg.PublicKey, cfg.PrivateKey)
ecfg.KSK, ecfg.KSKPrivate, err = s.loadKey(cfg.PublicKey, cfg.PrivateKey)
if err != nil {
return nil, err
}
ecfg.KSK = ksk
ecfg.KSKPrivate = kskPrivate
}
if cfg.ZonePublicKey != "" {
zsk, zskPrivate, err := s.loadKey(cfg.ZonePublicKey, cfg.ZonePrivateKey)
ecfg.ZSK, ecfg.ZSKPrivate, err = s.loadKey(cfg.ZonePublicKey, cfg.ZonePrivateKey)
if err != nil {
return nil, err
}
ecfg.ZSK = zsk
ecfg.ZSKPrivate = zskPrivate
}
if ecfg.KSK != nil && ecfg.ZSK == nil {
@ -141,19 +133,6 @@ func (s *Server) Start() error {
return nil
}
func (s *Server) Run() {
s.Start()
// wait
sig := make(chan os.Signal)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
for {
s := <-sig
fmt.Printf("Signal %v received, stopping.", s)
break
}
}
func (s *Server) doRunListener(ds *dns.Server) {
err := ds.ListenAndServe()
log.Fatale(err)

@ -1,6 +1,11 @@
package util
import "strings"
import "github.com/miekg/dns"
import "github.com/hlandau/madns/merr"
import "fmt"
import "regexp"
import "net/mail"
// Split a domain name a.b.c.d.e into parts e (the head) and a.b.c.d (the rest).
func SplitDomainHead(name string) (head, rest string) {
@ -30,4 +35,104 @@ func SplitDomainTail(name string) (tail, rest string) {
return s[0], s[1]
}
// For domains of the form "Y1.Y2...Yn.ANCHOR.X1.X2...Xn.", returns the following values:
//
// basename is the name appearing directly beneath the anchor (Yn).
//
// subname contains any additional labels appearing beneath the anchor (Y1 through Y(n-1)),
// separated by dots.
//
// rootname contains ANCHOR through Xn inclusive, separated by dots.
//
// If no label corresponds to ANCHOR, an error is returned.
// If ANCHOR is the first label, basename is an empty string.
//
// Examples, where anchor="bit":
// "a.b.c.d." -> merr.ErrNotInZone
// "a.b.c.d.bit." -> subname="a.b.c", basename="d", rootname="bit"
// "d.bit." -> subname="", basename="d", rootname="bit"
// "bit." -> subname="", basename="", rootname="bit"
// "bit.x.y.z." -> subname="", basename="", rootname="bit.x.y.z"
// "d.bit.x.y.z." -> subname="", basename="d", rootname="bit.x.y.z"
// "c.d.bit.x.y.z." -> subname="c", basename="d", rootname="bit.x.y.z"
// "a.b.c.d.bit.x.y.z." -> subname="a.b.c", basename="d", rootname="bit.x.y.z"
func SplitDomainByFloatingAnchor(qname, anchor string) (subname, basename, rootname string, err error) {
qname = strings.TrimRight(qname, ".")
parts := strings.Split(qname, ".")
if len(parts) < 2 {
if parts[0] != anchor {
err = merr.ErrNotInZone
return
}
rootname = parts[0]
return
}
for i := len(parts) - 1; i >= 0; i-- {
v := parts[i]
// scanning for rootname
if v == anchor {
if i == 0 {
// i is alreay zero, so we have something like bit.x.y.z.
rootname = qname
return
}
rootname = strings.Join(parts[i:len(parts)], ".")
basename = parts[i-1]
subname = strings.Join(parts[0:i-1], ".")
return
}
}
err = merr.ErrNotInZone
return
}
// Convert a domain name basename (e.g. "example") to a Namecoin domain name
// key name ("d/example").
func BasenameToNamecoinKey(basename string) (string, error) {
return "d/" + basename, nil
}
// Convert a Namecoin domain name key name (e.g. "d/example") to a domain name
// basename ("example").
func NamecoinKeyToBasename(key string) (string, error) {
if strings.HasPrefix(key, "d/") {
return key[2:], nil
}
return "", fmt.Errorf("not a domain name key")
}
// This is used to validate NS records, targets in SRV records, etc. In these cases
// an IP address is not allowed. Therefore this regex must exclude all-numeric domain names.
// This is done by requiring the final part to start with an alphabetic character.
var re_hostName = regexp.MustCompilePOSIX(`^(([a-z0-9_][a-z0-9_-]{0,62}\.)*[a-z_][a-z0-9_-]{0,62}\.?|\.)$`)
var re_label = regexp.MustCompilePOSIX(`^[a-z_][a-z0-9_-]*$`)
var re_serviceName = regexp.MustCompilePOSIX(`^[a-z_][a-z0-9_-]*$`)
func ValidateHostName(name string) bool {
name = dns.Fqdn(name)
return len(name) <= 255 && re_hostName.MatchString(name)
}
func ValidateLabel(name string) bool {
return len(name) <= 63 && re_label.MatchString(name)
}
func ValidateServiceName(name string) bool {
return len(name) < 63 && re_serviceName.MatchString(name)
}
func ValidateEmail(email string) bool {
addr, err := mail.ParseAddress(email)
if addr == nil || err != nil {
return false
}
return addr.Name == ""
}
// © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later

@ -2,6 +2,7 @@ package util_test
import "testing"
import "github.com/hlandau/ncdns/util"
import "github.com/hlandau/madns/merr"
type item struct {
input string
@ -39,3 +40,44 @@ func TestSplitDomainHead(t *testing.T) {
}
}
}
type aitem struct {
input string
anchor string
expectedSubname string
expectedBasename string
expectedRootname string
expectedError error
}
var aitems = []aitem{
aitem{"", "bit", "", "", "", merr.ErrNotInZone},
aitem{".", "bit", "", "", "", merr.ErrNotInZone},
aitem{"d.", "bit", "", "", "", merr.ErrNotInZone},
aitem{"a.b.c.d.", "bit", "", "", "", merr.ErrNotInZone},
aitem{"a.b.c.d.bit.", "bit", "a.b.c", "d", "bit", nil},
aitem{"d.bit.", "bit", "", "d", "bit", nil},
aitem{"bit.", "bit", "", "", "bit", nil},
aitem{"bit.x.y.z.", "bit", "", "", "bit.x.y.z", nil},
aitem{"d.bit.x.y.z.", "bit", "", "d", "bit.x.y.z", nil},
aitem{"c.d.bit.x.y.z.", "bit", "c", "d", "bit.x.y.z", nil},
aitem{"a.b.c.d.bit.x.y.z.", "bit", "a.b.c", "d", "bit.x.y.z", nil},
}
func TestSplitDomainByFloatingAnchor(t *testing.T) {
for i, it := range aitems {
subname, basename, rootname, err := util.SplitDomainByFloatingAnchor(it.input, it.anchor)
if subname != it.expectedSubname {
t.Errorf("Item %d: subname \"%s\" does not equal expected value \"%s\"", i, subname, it.expectedSubname)
}
if basename != it.expectedBasename {
t.Errorf("Item %d: basename \"%s\" does not equal expected value \"%s\"", i, basename, it.expectedBasename)
}
if rootname != it.expectedRootname {
t.Errorf("Item %d: rootname \"%s\" does not equal expected value \"%s\"", i, basename, it.expectedRootname)
}
if err != it.expectedError {
t.Errorf("Item %d: error \"%s\" does not equal expected error \"%s\"", i, err, it.expectedError)
}
}
}

Loading…
Cancel
Save