package host import ( "fmt" "net" "os" "regexp" "strings" "git.giaever.org/joachimmg/go-log.git/log" "git.giaever.org/joachimmg/m-dns/errors" ) type HostString interface { String() string Empty() bool dotted() string } type Instance interface { HostString InstanceAddr(sr Service, d Domain) HostString } type Service interface { HostString Types() []string RootType() string ServiceAddr(d Domain) HostString } type Domain interface { HostString DiscoveryAddr() HostString } type Hostname interface { HostString HostnameAddr(d Domain) HostString } type TXT interface { HostString } type String string const ( EmptyString String = "" ) func (s String) String() string { return string(s) } func (s String) Empty() bool { return len(s) == 0 } func (s String) isValid() error { log.Traceln(errors.HostString, s) if s.Empty() { log.Traceln(errors.HostString, s, errors.HostStringIsEmpty) return errors.HostStringIsEmpty } if s[len(s)-1] == '.' { log.Traceln(errors.HostString, s, errors.HostStringIsInvalid) return errors.HostStringIsInvalid } return nil } func (s String) IsInstanceVariable() (Instance, error) { if s.Empty() { log.Traceln(errors.HostString, errors.HostStringIsInvalidInstance) return EmptyString, errors.HostStringIsInvalidInstance } re := regexp.MustCompile(`^[\x20-\x7E]+$`) if rs := re.FindAllStringSubmatch(s.String(), 1); len(rs) != 0 { return s, nil } log.Traceln(errors.HostString, errors.HostStringIsInvalidInstance) return EmptyString, errors.HostStringIsInvalidInstance } func (s String) IsServiceVariable() (Service, error) { if err := s.isValid(); err != nil { log.Traceln(errors.HostString, errors.HostStringIsInvalidService, err) return EmptyString, errors.HostStringIsInvalidService } // RFC 6763: Service pair _._ (including (_sub.+)._name._type) re := regexp.MustCompile(`^((?:(\_[a-z\-]+)\.)+)(?:(\_+(?:tcp|udp))+)$`) if rs := re.FindAllStringSubmatch(s.String(), 1); len(rs) != 0 { switch rs[0][3] { case "_tcp", "_udp": return s, nil } } log.Traceln(errors.HostString, errors.HostStringIsInvalidService) return EmptyString, errors.HostStringIsInvalidService } func (s String) IsDomainVariable() (Domain, error) { if err := s.isValid(); err != nil { log.Traceln(errors.HostString, errors.HostStringIsInvalidDomain) return EmptyString, errors.HostStringIsInvalidDomain } if s == "local" { return s, nil } if _, err := net.LookupHost(s.String()); err != nil { log.Traceln(errors.HostString, err) return EmptyString, errors.HostStringIsInvalidDomain } return s, nil } func (s String) IsHostnameVariable() (Hostname, error) { if err := s.isValid(); err != nil { log.Traceln(errors.HostString, errors.HostStringIsInvalidHostname, err) return EmptyString, errors.HostStringIsInvalidHostname } if hostname, _ := os.Hostname(); s.String() == hostname { return s, nil } if _, err := net.LookupHost(s.String()); err != nil { log.Traceln(errors.HostString, err) return EmptyString, errors.HostStringIsInvalidHostname } return s, nil } func (s String) IsTxtVariable() (TXT, error) { log.Traceln(errors.HostString, s) if s.Empty() || s[:1][0] == '=' { return EmptyString, nil } if len(s) > 200 { return EmptyString, errors.HostTXTExceedsLimit } // RFC 6763: Spaces in key is significant, can include any character(incl. '=') in value re := regexp.MustCompile(`^([\x20-\x3C\x3E-\x7E]+)(?:(\=+)?([\x20-\x7E]+)?)$`) if rs := re.FindAllStringSubmatch(s.String(), 1); len(rs) != 0 { return String(fmt.Sprintf("%s%s%s", strings.Trim(rs[0][1], " "), rs[0][2], rs[0][3])), nil } return EmptyString, nil } func (s String) dotted() string { return s.String() + "." } func (s String) ServiceAddr(d Domain) HostString { return String(s.dotted() + d.dotted()) } func (s String) InstanceAddr(sr Service, d Domain) HostString { return String(s.encodedInstance().dotted() + sr.dotted() + d.dotted()) } func (s String) HostnameAddr(d Domain) HostString { return String(s.dotted() + d.dotted()) } func (s String) DiscoveryAddr() HostString { return String(String("_services._dns-sd._udp").dotted() + s.dotted()) } func (s String) encodedInstance() String { // RFC 6763, 4.3: Must be escaped, except leading _ return String(strings.Replace(regexp.QuoteMeta(s.String()), "_", `\_`, -1)) } func (s String) Types() []string { return strings.Split(s.String(), ".") } func (s String) RootType() string { t := s.Types() return t[len(t)-1] }