refactoring

pull/18/head
Hugo Landau 10 years ago
parent e974fb4c9e
commit 1fa8db652c

@ -374,83 +374,7 @@ func (tx *btx) _findNCValue(ncv *ncValue, isubname, subname string, depth int,
}
func (tx *btx) addAnswersUnderNCValueActual(ncv *ncValue, sn string) (rrs []dns.RR, err error) {
// A
ips, err := ncv.GetIPs()
if err != nil {
return
}
for _, ip := range ips {
pip := net.ParseIP(ip)
if pip == nil || pip.To4() == nil {
continue
}
rrs = append(rrs, &dns.A{
Hdr: dns.RR_Header{Name: dns.Fqdn(tx.qname), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 600},
A: pip})
}
// AAAA
ips, err = ncv.GetIP6s()
if err != nil {
return
}
for _, ip := range ips {
pip := net.ParseIP(ip)
if pip == nil || pip.To4() != nil {
continue
}
rrs = append(rrs, &dns.AAAA{
Hdr: dns.RR_Header{Name: dns.Fqdn(tx.qname), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 600},
AAAA: pip})
}
// NS
nss, err := ncv.GetNSs()
if err != nil {
return
}
for _, ns := range nss {
ns = dns.Fqdn(ns)
rrs = append(rrs, &dns.NS{
Hdr: dns.RR_Header{Name: dns.Fqdn(tx.qname), Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 600},
Ns: ns})
}
// TXT
txts, err := ncv.GetTXTs()
if err != nil {
return
}
for _, txt := range txts {
rrs = append(rrs, &dns.TXT{
Hdr: dns.RR_Header{Name: dns.Fqdn(tx.qname), Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 600},
Txt: txt})
}
// TODO: MX
// TODO: SRV
// DS
dss, err := ncv.GetDSs()
if err != nil {
return
}
for i := range dss {
dss[i].Hdr.Name = dns.Fqdn(tx.qname)
rrs = append(rrs, &dss[i])
}
if len(rrs) == 0 {
if m, ok := ncv.Map[""]; ok {
return tx.addAnswersUnderNCValueActual(m, sn)
}
}
rrs = convertAt(nil, dns.Fqdn(tx.qname), ncv)
return
}

@ -1,7 +1,9 @@
package backend
/*
import "github.com/miekg/dns"
import "net"
import "regexp"
// Experimental attempt to factor out the JSON->DNS conversion function.
// Currently used only by namesync, not ncdns.
@ -15,40 +17,64 @@ func Convert(suffix string, jsonValue string) ([]dns.RR, error) {
}
rootNCV := d.ncv
rrs := convertRecursive(suffix, rootNCV, 0)
rrs := convertRecursive(nil, suffix, rootNCV, 0)
return rrs, nil
}
// Try and tolerate errors.
func convertRecursive(suffix string, ncv *ncValue, depth int) (rrs []dns.RR) {
func convertRecursive(out []dns.RR, suffix string, ncv *ncValue, depth int) []dns.RR {
if depth > 64 {
return
return out
}
rrs = append(rrs, convertIPs(suffix, ncv)...)
rrs = append(rrs, convertIP6s(suffix, ncv)...)
//rrs = append(rrs, ...convertServices(suffix, ncv))
//rrs = append(rrs, ...convertAlias(suffix, ncv))
rrs = append(rrs, convertNSs(suffix, ncv)...)
out = convertAt(out, suffix, ncv)
for k, v := range ncv.Map {
subsuffix := k + "." + suffix
if k == "" {
subsuffix = suffix
}
rrs = append(rrs, convertRecursive(subsuffix, v, depth+1)...)
out = convertRecursive(out, subsuffix, v, depth+1)
}
rrs = append(rrs, convertDSs(suffix, ncv)...)
//rrs = append(rrs, ...convertTXT(suffix, ncv))
return
return out
}
func convertIPs(suffix string, ncv *ncValue) (rrs []dns.RR) {
// Conversion at a specific NCV non-recursivey for all types
func convertAt(out []dns.RR, suffix string, ncv *ncValue) []dns.RR {
return convertAt_(out, suffix, ncv, 0)
}
func convertAt_(out []dns.RR, suffix string, ncv *ncValue, depth int) []dns.RR {
if depth > 1 {
return out
}
out = convertIPs(out, suffix, ncv)
out = convertIP6s(out, suffix, ncv)
out = convertNSs(out, suffix, ncv)
out = convertDSs(out, suffix, ncv)
out = convertTXTs(out, suffix, ncv)
// XXX: should this apply only if no records were added above?
if m, ok := ncv.Map[""]; ok {
out = convertAt_(out, suffix, m, depth+1)
}
// TODO: CNAME
// TODO: MX
// TODO: SRV
return out
}
// Conversion at a specific NCV non-recursively for specific types
func convertIPs(out []dns.RR, suffix string, ncv *ncValue) []dns.RR {
ips, err := ncv.GetIPs()
if err != nil {
return
return out
}
for _, ip := range ips {
@ -57,19 +83,19 @@ func convertIPs(suffix string, ncv *ncValue) (rrs []dns.RR) {
continue
}
rrs = append(rrs, &dns.A{
out = append(out, &dns.A{
Hdr: dns.RR_Header{Name: dns.Fqdn(suffix), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 600},
A: pip,
})
}
return
return out
}
func convertIP6s(suffix string, ncv *ncValue) (rrs []dns.RR) {
func convertIP6s(out []dns.RR, suffix string, ncv *ncValue) []dns.RR {
ips, err := ncv.GetIP6s()
if err != nil {
return
return out
}
for _, ip := range ips {
@ -78,42 +104,71 @@ func convertIP6s(suffix string, ncv *ncValue) (rrs []dns.RR) {
continue
}
rrs = append(rrs, &dns.AAAA{
out = append(out, &dns.AAAA{
Hdr: dns.RR_Header{Name: dns.Fqdn(suffix), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 600},
AAAA: pip,
})
}
return
return out
}
func convertNSs(suffix string, ncv *ncValue) (rrs []dns.RR) {
func convertNSs(out []dns.RR, suffix string, ncv *ncValue) []dns.RR {
nss, err := ncv.GetNSs()
if err != nil {
return
return out
}
for _, ns := range nss {
if !validateHostName(ns) {
continue
}
ns = dns.Fqdn(ns)
rrs = append(rrs, &dns.NS{
out = append(out, &dns.NS{
Hdr: dns.RR_Header{Name: dns.Fqdn(suffix), Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 600},
Ns: ns,
})
}
return
return out
}
func convertTXTs(out []dns.RR, suffix string, ncv *ncValue) []dns.RR {
txts, err := ncv.GetTXTs()
if err != nil {
return out
}
for _, txt := range txts {
out = append(out, &dns.TXT{
Hdr: dns.RR_Header{Name: dns.Fqdn(suffix), Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 600},
Txt: txt,
})
}
return out
}
func convertDSs(suffix string, ncv *ncValue) (rrs []dns.RR) {
func convertDSs(out []dns.RR, suffix string, ncv *ncValue) []dns.RR {
dss, err := ncv.GetDSs()
if err != nil {
return
return out
}
for i := range dss {
dss[i].Hdr.Name = dns.Fqdn(suffix)
rrs = append(rrs, &dss[i])
out = append(out, &dss[i])
}
return
return out
}
// Validation functions
var re_hostName = 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)
}*/

@ -0,0 +1,746 @@
package ncdomain
import "encoding/json"
import "net"
import "fmt"
import "github.com/miekg/dns"
import "encoding/base64"
import "encoding/hex"
import "regexp"
import "net/mail"
const depthLimit = 16
const mergeDepthLimit = 4
type Value struct {
IP []net.IP
IP6 []net.IP
NS []string
Alias string
Translate string
DS []*dns.DS
TXT [][]string
Service []*dns.SRV // header name contains e.g. "_http._tcp"
Hostmaster string // "hostmaster@example.com"
MX []*dns.MX // header name is left blank
Map map[string]*Value // may contain and "*", will not contain ""
}
func (v *Value) RRs(out []dns.RR, suffix string) ([]dns.RR, error) {
il := len(out)
suffix = dns.Fqdn(suffix)
out, _ = v.appendIPs(out, suffix)
out, _ = v.appendIP6s(out, suffix)
out, _ = v.appendNSs(out, suffix)
out, _ = v.appendTXTs(out, suffix)
out, _ = v.appendDSs(out, suffix)
out, _ = v.appendServices(out, suffix)
out, _ = v.appendMXs(out, suffix)
out, _ = v.appendAlias(out, suffix)
out, _ = v.appendTranslate(out, suffix)
xout := out[il:]
for i := range xout {
xout[i].Header().Name = suffix
}
return out, nil
}
func (v *Value) appendIPs(out []dns.RR, suffix string) ([]dns.RR, error) {
for _, ip := range v.IP {
out = append(out, &dns.A{
Hdr: dns.RR_Header{
Name: suffix,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 600,
},
A: ip,
})
}
return out, nil
}
func (v *Value) appendIP6s(out []dns.RR, suffix string) ([]dns.RR, error) {
for _, ip := range v.IP6 {
out = append(out, &dns.AAAA{
Hdr: dns.RR_Header{
Name: suffix,
Rrtype: dns.TypeAAAA,
Class: dns.ClassINET,
Ttl: 600,
},
AAAA: ip,
})
}
return out, nil
}
func (v *Value) appendNSs(out []dns.RR, suffix string) ([]dns.RR, error) {
for _, ns := range v.NS {
out = append(out, &dns.NS{
Hdr: dns.RR_Header{
Name: suffix,
Rrtype: dns.TypeNS,
Class: dns.ClassINET,
Ttl: 600,
},
Ns: ns,
})
}
return out, nil
}
func (v *Value) appendTXTs(out []dns.RR, suffix string) ([]dns.RR, error) {
for _, txt := range v.TXT {
out = append(out, &dns.TXT{
Hdr: dns.RR_Header{
Name: suffix,
Rrtype: dns.TypeTXT,
Class: dns.ClassINET,
Ttl: 600,
},
Txt: txt,
})
}
return out, nil
}
func (v *Value) appendDSs(out []dns.RR, suffix string) ([]dns.RR, error) {
for _, ds := range v.DS {
out = append(out, ds)
}
return out, nil
}
func (v *Value) appendMXs(out []dns.RR, suffix string) ([]dns.RR, error) {
for _, mx := range v.MX {
out = append(out, mx)
}
return out, nil
}
func (v *Value) appendServices(out []dns.RR, suffix string) ([]dns.RR, error) {
for _, svc := range v.Service {
out = append(out, svc)
}
return out, nil
}
func (v *Value) appendAlias(out []dns.RR, suffix string) ([]dns.RR, error) {
if v.Alias != "" {
out = append(out, &dns.CNAME{
Hdr: dns.RR_Header{
Name: suffix,
Rrtype: dns.TypeCNAME,
Class: dns.ClassINET,
Ttl: 600,
},
Target: v.Alias,
})
}
return out, nil
}
func (v *Value) appendTranslate(out []dns.RR, suffix string) ([]dns.RR, error) {
if v.Translate != "" {
out = append(out, &dns.DNAME{
Hdr: dns.RR_Header{
Name: suffix,
Rrtype: dns.TypeDNAME,
Class: dns.ClassINET,
Ttl: 600,
},
Target: v.Translate,
})
}
return out, nil
}
func (v *Value) RRsRecursive(out []dns.RR, suffix string) ([]dns.RR, error) {
out, err := v.RRs(out, suffix)
if err != nil {
return nil, err
}
for mk, mv := range v.Map {
out, err = mv.RRsRecursive(out, mk+"."+suffix)
if err != nil {
return nil, err
}
}
return out, nil
}
type rawValue struct {
IP interface{} `json:"ip"`
IP6 interface{} `json:"ip6"`
NS interface{} `json:"ns"`
DNS interface{} `json:"dns"` // actually an alias for NS
Alias interface{} `json:"alias"`
Translate interface{} `json:"translate"`
DS interface{} `json:"ds"`
TXT interface{} `json:"txt"`
Hostmaster interface{} `json:"email"` // Hostmaster
MX interface{} `json:"mx"`
Map json.RawMessage `json:"map"`
Service interface{} `json:"service"`
Import interface{} `json:"import"`
Delegate interface{} `json:"delegate"`
}
type ResolveFunc func(name string) (string, error)
// Call to convert a given JSON value to a parsed Namecoin domain value.
//
// If ResolveFunc is given, it will be called to obtain the values for domains
// referenced by "import" and "delegate" statements. The name passed is in
// Namecoin form (e.g. "d/example"). The JSON value or an error should be
// returned. If no ResolveFunc is passed, "import" and "delegate" statements
// always fail.
func ParseValue(jsonValue string, resolve ResolveFunc) (value *Value, err error) {
rv := &rawValue{}
v := &Value{}
err = json.Unmarshal([]byte(jsonValue), rv)
if err != nil {
return
}
if resolve == nil {
resolve = func(name string) (string, error) {
return "", fmt.Errorf("not supported")
}
}
rv.parse(v, resolve, 0, 0)
value = v
return
}
func (rv *rawValue) parse(v *Value, resolve ResolveFunc, depth, mergeDepth int) error {
if depth > depthLimit {
return fmt.Errorf("depth limit exceeded")
}
ok, _ := rv.parseDelegate(v, resolve, depth, mergeDepth)
if ok {
return nil
}
rv.parseImport(v, resolve, depth, mergeDepth)
rv.parseIP(v, rv.IP, false)
rv.parseIP(v, rv.IP6, true)
rv.parseNS(v)
rv.parseAlias(v)
rv.parseTranslate(v)
rv.parseHostmaster(v)
rv.parseDS(v)
rv.parseTXT(v)
rv.parseService(v)
rv.parseMX(v)
rv.parseMap(v, resolve, depth, mergeDepth)
v.moveEmptyMapItems()
return nil
}
func (rv *rawValue) parseMerge(mergeValue string, v *Value, resolve ResolveFunc, depth, mergeDepth int) error {
rv2 := &rawValue{}
if mergeDepth > mergeDepthLimit {
return fmt.Errorf("merge depth limit exceeded")
}
err := json.Unmarshal([]byte(mergeValue), rv2)
if err != nil {
return err
}
return rv2.parse(v, resolve, depth, mergeDepth)
}
func (rv *rawValue) parseIP(v *Value, ipi interface{}, ipv6 bool) {
if ipi != nil {
if ipv6 {
v.IP6 = nil
} else {
v.IP = nil
}
}
if ipa, ok := ipi.([]interface{}); ok {
for _, ip := range ipa {
if ips, ok := ip.(string); ok {
rv.addIP(v, ips, ipv6)
}
}
return
}
if ip, ok := ipi.(string); ok {
rv.addIP(v, ip, ipv6)
}
}
func (rv *rawValue) addIP(v *Value, ips string, ipv6 bool) error {
pip := net.ParseIP(ips)
if pip == nil || (pip.To4() == nil) != ipv6 {
return fmt.Errorf("malformed IP")
}
if ipv6 {
v.IP6 = append(v.IP6, pip)
} else {
v.IP = append(v.IP, pip)
}
return nil
}
func (rv *rawValue) parseNS(v *Value) error {
// "dns" takes precedence
if rv.DNS != nil {
rv.NS = rv.DNS
}
if rv.NS == nil {
return nil
}
v.NS = nil
switch rv.NS.(type) {
case []interface{}:
for _, si := range rv.NS.([]interface{}) {
s, ok := si.(string)
if !ok || !validateHostName(s) {
continue
}
v.NS = append(v.NS, s)
}
return nil
case string:
s := rv.NS.(string)
if !validateHostName(s) {
return fmt.Errorf("malformed NS hostname")
}
v.NS = append(v.NS, s)
return nil
default:
return fmt.Errorf("unknown NS field format")
}
}
func (rv *rawValue) parseAlias(v *Value) error {
if rv.Alias == nil {
return nil
}
if s, ok := rv.Alias.(string); ok {
if !validateHostName(s) {
return fmt.Errorf("malformed hostname in alias field")
}
v.Alias = s
return nil
}
return fmt.Errorf("unknown alias field format")
}
func (rv *rawValue) parseTranslate(v *Value) error {
if rv.Translate == nil {
return nil
}
if s, ok := rv.Translate.(string); ok {
if !validateHostName(s) {
return fmt.Errorf("malformed hostname in translate field")
}
v.Translate = s
return nil
}
return fmt.Errorf("unknown translate field format")
}
func (rv *rawValue) parseImport(v *Value, resolve ResolveFunc, depth, mergeDepth int) error {
if rv.Import == nil {
return nil
}
if s, ok := rv.Import.(string); ok {
dv, err := resolve(s)
if err == nil {
err = rv.parseMerge(dv, v, resolve, depth, mergeDepth+1)
}
return err
}
return fmt.Errorf("unknown import field format")
}
func (rv *rawValue) parseDelegate(v *Value, resolve ResolveFunc, depth, mergeDepth int) (bool, error) {
if rv.Delegate == nil {
return false, nil
}
if s, ok := rv.Delegate.(string); ok {
dv, err := resolve(s)
if err == nil {
err = rv.parseMerge(dv, v, resolve, depth, mergeDepth+1)
}
return true, err
}
return false, fmt.Errorf("unknown delegate field format")
}
func (rv *rawValue) parseHostmaster(v *Value) error {
if rv.Translate == nil {
return nil
}
if s, ok := rv.Hostmaster.(string); ok {
if !validateEmail(s) {
return fmt.Errorf("malformed e. mail address in email field")
}
v.Hostmaster = s
return nil
}
return fmt.Errorf("unknown email field format")
}
func (rv *rawValue) parseDS(v *Value) error {
if rv.DS == nil {
return nil
}
v.DS = nil
if dsa, ok := rv.DS.([]interface{}); ok {
for _, ds1 := range dsa {
if ds, ok := ds1.([]interface{}); ok {
if len(ds) != 4 {
continue
}
a1, ok := ds[0].(float64)
if !ok {
continue
}
a2, ok := ds[1].(float64)
if !ok {
continue
}
a3, ok := ds[2].(float64)
if !ok {
continue
}
a4, ok := ds[3].(string)
if !ok {
continue
}
a4b, err := base64.StdEncoding.DecodeString(a4)
if err != nil {
continue
}
a4h := hex.EncodeToString(a4b)
v.DS = append(v.DS, &dns.DS{
Hdr: dns.RR_Header{Rrtype: dns.TypeDS, Class: dns.ClassINET, Ttl: 600},
KeyTag: uint16(a1),
Algorithm: uint8(a2),
DigestType: uint8(a3),
Digest: a4h,
})
}
}
}
return fmt.Errorf("malformed DS field format")
}
func (rv *rawValue) parseTXT(v *Value) error {
if rv.TXT == nil {
return nil
}
if txta, ok := rv.TXT.([]interface{}); ok {
// ["...", "..."] or [["...", "..."], ["...", "..."]]
for _, vv := range txta {
if sa, ok := vv.([]interface{}); ok {
// [["...", "..."], ["...", "..."]]
a := []string{}
for _, x := range sa {
if xs, ok := x.(string); ok {
a = append(a, xs)
}
}
v.TXT = append(v.TXT, a)
} else if s, ok := vv.(string); ok {
v.TXT = append(v.TXT, segmentizeTXT(s))
} else {
return fmt.Errorf("malformed TXT value")
}
}
} else {
// "..."
if s, ok := rv.TXT.(string); ok {
v.TXT = append(v.TXT, segmentizeTXT(s))
} else {
return fmt.Errorf("malformed TXT value")
}
}
return nil
}
func segmentizeTXT(txt string) (a []string) {
for len(txt) > 255 {
a = append(a, txt[0:255])
txt = txt[255:]
}
a = append(a, txt)
return
}
func (rv *rawValue) parseMX(v *Value) error {
if rv.MX == nil {
return nil
}
if sa, ok := rv.MX.([]interface{}); ok {
for _, s := range sa {
rv.parseSingleMX(s, v)
}
}
return fmt.Errorf("malformed MX value")
}
func (rv *rawValue) parseSingleMX(s interface{}, v *Value) error {
sa, ok := s.([]interface{})
if !ok {
return fmt.Errorf("malformed MX value")
}
if len(sa) < 2 {
return fmt.Errorf("malformed MX value")
}
prio, ok := sa[0].(float64)
if !ok || prio < 0 {
return fmt.Errorf("malformed MX value")
}
hostname, ok := sa[1].(string)
if !ok || !validateHostName(hostname) {
return fmt.Errorf("malformed MX value")
}
v.MX = append(v.MX, &dns.MX{
Hdr: dns.RR_Header{Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 600},
Preference: uint16(prio),
Mx: hostname,
})
return nil
}
func (rv *rawValue) parseService(v *Value) error {
if rv.Service == nil {
return nil
}
if sa, ok := rv.Service.([]interface{}); ok {
for _, s := range sa {
rv.parseSingleService(s, v)
}
}
return fmt.Errorf("malformed service value")
}
func (rv *rawValue) parseSingleService(svc interface{}, v *Value) error {
svca, ok := svc.([]interface{})
if !ok {
return fmt.Errorf("malformed service value")
}
if len(svca) < 6 {
return fmt.Errorf("malformed service value")
}
appProtoName, ok := svca[0].(string)
if !ok || !validateServiceName(appProtoName) {
return fmt.Errorf("malformed service value")
}
transportProtoName, ok := svca[1].(string)
if !ok || !validateServiceName(transportProtoName) {
return fmt.Errorf("malformed service value")
}
priority, ok := svca[2].(float64)
if !ok {
return fmt.Errorf("malformed service value")
}
weight, ok := svca[3].(float64)
if !ok {
return fmt.Errorf("malformed service value")
}
port, ok := svca[4].(float64)
if !ok {
return fmt.Errorf("malformed service value")
}
hostname, ok := svca[5].(string)
if !ok || !validateHostName(hostname) {
return fmt.Errorf("malformed service value")
}
v.Service = append(v.Service, &dns.SRV{
Hdr: dns.RR_Header{
Name: "_" + appProtoName + "._" + transportProtoName,
Rrtype: dns.TypeSRV,
Class: dns.ClassINET,
Ttl: 600,
},
Priority: uint16(priority),
Weight: uint16(weight),
Port: uint16(port),
Target: hostname,
})
return nil
}
func (rv *rawValue) parseMap(v *Value, resolve ResolveFunc, depth, mergeDepth int) error {
m := map[string]json.RawMessage{}
err := json.Unmarshal(rv.Map, &m)
if err != nil {
return err
}
for mk, mv := range m {
rv2 := &rawValue{}
v2 := &Value{}
var s string
err := json.Unmarshal(mv, &s)
if err == nil {
// deprecated case: "map": { "": "127.0.0.1" }
rv2.IP = s
rv2.IP6 = s
} else {
// normal case: "map": { "": { ... } }
err = json.Unmarshal(mv, rv2)
if err != nil {
continue
}
}
rv2.parse(v2, resolve, depth+1, mergeDepth)
if v.Map == nil {
v.Map = make(map[string]*Value)
}
v.Map[mk] = v2
}
return nil
}
// Moves items in {"map": {"": ...}} to the object itself, then deletes the ""
// entry in the map object.
func (v *Value) moveEmptyMapItems() error {
if ev, ok := v.Map[""]; ok {
if len(v.IP) == 0 {
v.IP = ev.IP
}
if len(v.IP6) == 0 {
v.IP6 = ev.IP6
}
if len(v.NS) == 0 {
v.NS = ev.NS
}
if len(v.DS) == 0 {
v.DS = ev.DS
}
if len(v.TXT) == 0 {
v.TXT = ev.TXT
}
if len(v.Service) == 0 {
v.Service = ev.Service
}
if len(v.MX) == 0 {
v.MX = ev.MX
}
if len(v.Alias) == 0 {
v.Alias = ev.Alias
}
if len(v.Translate) == 0 {
v.Translate = ev.Translate
}
if len(v.Hostmaster) == 0 {
v.Hostmaster = ev.Hostmaster
}
delete(v.Map, "")
if len(v.Map) == 0 {
v.Map = ev.Map
}
}
return nil
}
// Validation functions
var re_hostName = regexp.MustCompilePOSIX(`^([a-z0-9_-]+\.)*[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 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 == ""
}

@ -0,0 +1,182 @@
package ncdomain_test
import "github.com/hlandau/ncdns/convert"
import "github.com/miekg/dns"
import "testing"
import "net"
import "fmt"
type item struct {
jsonValue string
value *convert.Value
expectedError error
merges map[string]string
}
var suite = []item{
item{`{}`, &convert.Value{}, nil, nil},
item{`{"ip":"1.2.3.4"}`, &convert.Value{IP: []net.IP{net.ParseIP("1.2.3.4")}}, nil, nil},
item{`{"ip":["1.2.3.4"]}`, &convert.Value{IP: []net.IP{net.ParseIP("1.2.3.4")}}, nil, nil},
item{`{"ip":["1.2.3.4","200.200.200.200"]}`, &convert.Value{IP: []net.IP{net.ParseIP("1.2.3.4"), net.ParseIP("200.200.200.200")}}, nil, nil},
item{`{"ip6":"dead:b33f::deca:fbad"}`, &convert.Value{IP6: []net.IP{net.ParseIP("dead:b33f::deca:fbad")}}, nil, nil},
item{`{"ip6":["dead:b33f::deca:fbad"]}`, &convert.Value{IP6: []net.IP{net.ParseIP("dead:b33f::deca:fbad")}}, nil, nil},
item{`{"ip6":["dead:b33f::deca:fbad","1234:abcd:5678:bcde:9876:fedc:5432:ba98"]}`, &convert.Value{IP6: []net.IP{net.ParseIP("dead:b33f::deca:fbad"), net.ParseIP("1234:abcd:5678:bcde:9876:fedc:5432:ba98")}}, nil, nil},
item{`{"ns":"alpha.beta.gamma.delta"}`, &convert.Value{NS: []string{"alpha.beta.gamma.delta"}}, nil, nil},
item{`{"ns":["alpha.beta.gamma.delta"]}`, &convert.Value{NS: []string{"alpha.beta.gamma.delta"}}, nil, nil},
item{`{"ns":["alpha.beta.gamma.delta","delta.gamma.beta.alpha"]}`, &convert.Value{NS: []string{"alpha.beta.gamma.delta", "delta.gamma.beta.alpha"}}, nil, nil},
item{`{"mx":[[10,"alpha.beta.gamma.delta"]]}`, &convert.Value{MX: []dns.MX{dns.MX{Hdr: dns.RR_Header{Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 600}, Preference: 10, Mx: "alpha.beta.gamma.delta"}}}, nil, nil},
item{`{"mx":[[10,"alpha.beta.gamma.delta"],[20,"epsilon.example"]]}`, &convert.Value{MX: []dns.MX{dns.MX{Hdr: dns.RR_Header{Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 600}, Preference: 10, Mx: "alpha.beta.gamma.delta"}, dns.MX{Hdr: dns.RR_Header{Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 600}, Preference: 20, Mx: "epsilon.example"}}}, nil, nil},
item{`{"alias":"alpha.beta.gamma.delta"}`, &convert.Value{Alias: "alpha.beta.gamma.delta"}, nil, nil},
item{`{"translate":"alpha.beta.gamma.delta"}`, &convert.Value{Translate: "alpha.beta.gamma.delta"}, nil, nil},
item{`{"txt":"text record"}`, &convert.Value{TXT: [][]string{[]string{"text record"}}}, nil, nil},
item{`{"txt":"[text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record]"}`, &convert.Value{TXT: [][]string{[]string{"[text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record][text ... record]", "[text ... record]"}}}, nil, nil},
item{`{"txt":["text record"]}`, &convert.Value{TXT: [][]string{[]string{"text record"}}}, nil, nil},
item{`{"txt":["text record","text record 2"]}`, &convert.Value{TXT: [][]string{[]string{"text record"}, []string{"text record 2"}}}, nil, nil},
item{`{"txt":[["text", "record"]]}`, &convert.Value{TXT: [][]string{[]string{"text", "record"}}}, nil, nil},
item{`{"txt":[["text", "record"],["text", "record", "2"]]}`, &convert.Value{TXT: [][]string{[]string{"text", "record"}, []string{"text", "record", "2"}}}, nil, nil},
item{`{"service":[ ["http","tcp",1,2,80,"alpha.beta.gamma.delta"] ]}`, &convert.Value{Service: []dns.SRV{dns.SRV{Hdr: dns.RR_Header{Name: "_http._tcp", Ttl: 600, Rrtype: dns.TypeSRV, Class: dns.ClassINET}, Priority: 1, Weight: 2, Port: 80, Target: "alpha.beta.gamma.delta"}}}, nil, nil},
item{`{"service":[ ["http","tcp",1,2,80,"alpha.beta.gamma.delta"], ["https","tcp",1,2,443,"alpha.beta.gamma.delta"] ]}`, &convert.Value{Service: []dns.SRV{dns.SRV{Hdr: dns.RR_Header{Name: "_http._tcp", Ttl: 600, Rrtype: dns.TypeSRV, Class: dns.ClassINET}, Priority: 1, Weight: 2, Port: 80, Target: "alpha.beta.gamma.delta"}, dns.SRV{Hdr: dns.RR_Header{Name: "_https._tcp", Ttl: 600, Rrtype: dns.TypeSRV, Class: dns.ClassINET}, Priority: 1, Weight: 2, Port: 443, Target: "alpha.beta.gamma.delta"}}}, nil, nil},
item{`{"map":{ "": { } }}`, &convert.Value{}, nil, nil},
item{`{"map":{ "": { "ip": "1.2.3.4" } }}`, &convert.Value{IP: []net.IP{net.ParseIP("1.2.3.4")}}, nil, nil},
item{`{"map":{ "www": { "ip": "1.2.3.4" } }}`, &convert.Value{Map: map[string]*convert.Value{"www": &convert.Value{IP: []net.IP{net.ParseIP("1.2.3.4")}}}}, nil, nil},
item{`{"map":{ "": "1.2.3.4" }}`, &convert.Value{IP: []net.IP{net.ParseIP("1.2.3.4")}}, nil, nil},
item{`{"map":{ "www": "1.2.3.4" }}`, &convert.Value{Map: map[string]*convert.Value{"www": &convert.Value{IP: []net.IP{net.ParseIP("1.2.3.4")}}}}, nil, nil},
item{`{"ds":[[12345,8,2,"4tPJFvbe6scylOgmj7WIUESoM/xUWViPSpGEz8QaV2Y="]]}`, &convert.Value{DS: []dns.DS{dns.DS{Hdr: dns.RR_Header{Rrtype: dns.TypeDS, Class: dns.ClassINET, Ttl: 600}, KeyTag: 12345, Algorithm: 8, DigestType: 2, Digest: "e2d3c916f6deeac73294e8268fb5885044a833fc5459588f4a9184cfc41a5766"}}}, nil, nil},
item{`{"ds":[[54321,8,1,"5sFxbPtr3IToTOGrVRDaxpFztbI="],[12345,8,2,"4tPJFvbe6scylOgmj7WIUESoM/xUWViPSpGEz8QaV2Y="]]}`, &convert.Value{DS: []dns.DS{dns.DS{Hdr: dns.RR_Header{Rrtype: dns.TypeDS, Class: dns.ClassINET, Ttl: 600}, KeyTag: 54321, Algorithm: 8, DigestType: 1, Digest: "e6c1716cfb6bdc84e84ce1ab5510dac69173b5b2"}, dns.DS{Hdr: dns.RR_Header{Rrtype: dns.TypeDS, Class: dns.ClassINET, Ttl: 600}, KeyTag: 12345, Algorithm: 8, DigestType: 2, Digest: "e2d3c916f6deeac73294e8268fb5885044a833fc5459588f4a9184cfc41a5766"}}}, nil, nil},
item{`{"email":"hostmaster@example.com"}`, &convert.Value{Hostmaster: "hostmaster@example.com"}, nil, nil},
item{`{"ip":["1.2.3.4"],"import":"d/example"}`, &convert.Value{IP: []net.IP{net.ParseIP("1.2.3.4")}, IP6: []net.IP{net.ParseIP("::beef")}}, nil, map[string]string{"d/example": `{"ip6":["::beef"]}`}},
item{`{"ip":["1.2.3.4"],"import":"d/example"}`, &convert.Value{IP: []net.IP{net.ParseIP("1.2.3.4")}, IP6: []net.IP{net.ParseIP("::beef")}}, nil, map[string]string{"d/example": `{"ip":["2.3.4.5"],"ip6":["::beef"]}`}},
item{`{"ns":["alpha.beta"],"import":"d/example"}`, &convert.Value{NS: []string{"alpha.beta"}, IP6: []net.IP{net.ParseIP("::beef")}}, nil, map[string]string{"d/example": `{"ns":["gamma.delta"],"ip6":["::beef"]}`}},
item{`{"ds":[[12345,8,2,"4tPJFvbe6scylOgmj7WIUESoM/xUWViPSpGEz8QaV2Y="]],"import":"d/example"}`, &convert.Value{DS: []dns.DS{dns.DS{Hdr: dns.RR_Header{Rrtype: dns.TypeDS, Class: dns.ClassINET, Ttl: 600}, KeyTag: 12345, Algorithm: 8, DigestType: 2, Digest: "e2d3c916f6deeac73294e8268fb5885044a833fc5459588f4a9184cfc41a5766"}}}, nil, map[string]string{"d/example": `{"ds":[ [54321,8,1,"5sFxbPtr3IToTOGrVRDaxpFztbI="] ]}`}},
item{`{"import":"d/example"}`, &convert.Value{DS: []dns.DS{dns.DS{Hdr: dns.RR_Header{Rrtype: dns.TypeDS, Class: dns.ClassINET, Ttl: 600}, KeyTag: 54321, Algorithm: 8, DigestType: 1, Digest: "e6c1716cfb6bdc84e84ce1ab5510dac69173b5b2"}}}, nil, map[string]string{"d/example": `{"ds":[ [54321,8,1,"5sFxbPtr3IToTOGrVRDaxpFztbI="] ]}`}},
item{`{"ip":["1.2.3.4"],"delegate":"d/example"}`, &convert.Value{IP6: []net.IP{net.ParseIP("::beef")}}, nil, map[string]string{"d/example": `{"ip6":["::beef"]}`}},
}
func TestConversion(t *testing.T) {
for i, item := range suite {
resolve := func(name string) (string, error) {
if item.merges == nil {
return "", fmt.Errorf("not found")
}
if s, ok := item.merges[name]; ok {
return s, nil
} else {
return "", fmt.Errorf("not found")
}
}
v, err := convert.ParseValue(item.jsonValue, resolve)
if err != item.expectedError {
t.Errorf("Item %d did not match expected error: got %+v but expected %+v", i, err, item.expectedError)
}
if !equals(v, item.value) {
t.Errorf("Item %d value did not match expected value: got %+v but expected %+v", i, v, item.value)
}
}
}
// utility functions for testing equality
func equals(v1 *convert.Value, v2 *convert.Value) bool {
return (v1 != nil) == (v2 != nil) &&
eqIPArray(v1.IP, v2.IP) &&
eqIPArray(v1.IP6, v2.IP6) &&
eqStringArray(v1.NS, v2.NS) &&
v1.Alias == v2.Alias &&
eqDSArray(v1.DS, v2.DS) &&
eqStringArrayArray(v1.TXT, v2.TXT) &&
eqServiceArray(v1.Service, v2.Service) &&
eqValueMap(v1, v2)
}
func eqIPArray(a []net.IP, b []net.IP) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if !a[i].Equal(b[i]) {
return false
}
}
return true
}
func eqStringArray(a []string, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func eqDSArray(a []dns.DS, b []dns.DS) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if !eqDS(a[i], b[i]) {
return false
}
}
return true
}
func eqDS(a dns.DS, b dns.DS) bool {
return a.KeyTag == b.KeyTag && a.Algorithm == b.Algorithm &&
a.DigestType == b.DigestType && a.Digest == b.Digest && eqHdr(a.Hdr, b.Hdr)
}
func eqHdr(a dns.RR_Header, b dns.RR_Header) bool {
return a.Name == b.Name && a.Rrtype == b.Rrtype && a.Class == b.Class && a.Ttl == b.Ttl
}
func eqStringArrayArray(a [][]string, b [][]string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if !eqStringArray(a[i], b[i]) {
return false
}
}
return true
}
func eqServiceArray(a []dns.SRV, b []dns.SRV) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if !eqService(a[i], b[i]) {
return false
}
}
return true
}
func eqService(a dns.SRV, b dns.SRV) bool {
return a.Priority == b.Priority && a.Weight == b.Weight &&
a.Port == b.Port && a.Target == b.Target && eqHdr(a.Hdr, b.Hdr)
}
func eqValueMap(a *convert.Value, b *convert.Value) bool {
if len(a.Map) != len(b.Map) {
return false
}
for k, v1 := range a.Map {
v2, ok := b.Map[k]
if !ok {
return false
}
if !equals(v1, v2) {
return false
}
}
return true
}
Loading…
Cancel
Save