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 } type Instance interface { HostString EncodedInstance() string } type Service interface { HostString Types() []string RootType() string } type Domain interface { HostString } type Hostname interface { HostString } type TXT interface { HostString } type String string const ( EmptyString String = "" ) func (s String) String() string { return string(s) } func (s String) isEmpty() bool { return len(s) == 0 } func (s String) isValid() error { log.Traceln(errors.HostString, s) if s.isEmpty() { 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() (String, error) { if s.isEmpty() { 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() (String, 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() (String, 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() (String, error) { if err := s.isValid(); err != nil { log.Traceln(errors.HostString, errors.HostStringIsInvalidHostname, err) return EmptyString, errors.HostStringIsInvalidHostname } if hostname, err := os.Hostname(); err == nil { if 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() (String, error) { log.Traceln(errors.HostString, s) if s.isEmpty() || 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) EncodedInstance() string { // RFC 6763, 4.3: Must be escaped, except leading _ return 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] }