string.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. package host
  2. import (
  3. "fmt"
  4. "net"
  5. "os"
  6. "regexp"
  7. "strings"
  8. "git.giaever.org/joachimmg/go-log.git/log"
  9. "git.giaever.org/joachimmg/m-dns/errors"
  10. )
  11. type HostString interface {
  12. String() string
  13. }
  14. type Instance interface {
  15. HostString
  16. EncodedInstance() string
  17. }
  18. type Service interface {
  19. HostString
  20. Types() []string
  21. RootType() string
  22. }
  23. type Domain interface {
  24. HostString
  25. }
  26. type Hostname interface {
  27. HostString
  28. }
  29. type TXT interface {
  30. HostString
  31. }
  32. type String string
  33. const (
  34. EmptyString String = ""
  35. )
  36. func (s String) String() string {
  37. return string(s)
  38. }
  39. func (s String) isEmpty() bool {
  40. return len(s) == 0
  41. }
  42. func (s String) isValid() error {
  43. log.Traceln(errors.HostString, s)
  44. if s.isEmpty() {
  45. log.Traceln(errors.HostString, s, errors.HostStringIsEmpty)
  46. return errors.HostStringIsEmpty
  47. }
  48. if s[len(s)-1] == '.' {
  49. log.Traceln(errors.HostString, s, errors.HostStringIsInvalid)
  50. return errors.HostStringIsInvalid
  51. }
  52. return nil
  53. }
  54. func (s String) isInstanceVariable() (String, error) {
  55. if s.isEmpty() {
  56. log.Traceln(errors.HostString, errors.HostStringIsInvalidInstance)
  57. return EmptyString, errors.HostStringIsInvalidInstance
  58. }
  59. re := regexp.MustCompile(`^[\x20-\x7E]+$`)
  60. if rs := re.FindAllStringSubmatch(s.String(), 1); len(rs) != 0 {
  61. return s, nil
  62. }
  63. log.Traceln(errors.HostString, errors.HostStringIsInvalidInstance)
  64. return EmptyString, errors.HostStringIsInvalidInstance
  65. }
  66. func (s String) isServiceVariable() (String, error) {
  67. if err := s.isValid(); err != nil {
  68. log.Traceln(errors.HostString, errors.HostStringIsInvalidService, err)
  69. return EmptyString, errors.HostStringIsInvalidService
  70. }
  71. // RFC 6763: Service pair _<name>._<type> (including (_sub.+)._name._type)
  72. re := regexp.MustCompile(`^((?:(\_[a-z\-]+)\.)+)(?:(\_+(?:tcp|udp))+)$`)
  73. if rs := re.FindAllStringSubmatch(s.String(), 1); len(rs) != 0 {
  74. switch rs[0][3] {
  75. case "_tcp", "_udp":
  76. return s, nil
  77. }
  78. }
  79. log.Traceln(errors.HostString, errors.HostStringIsInvalidService)
  80. return EmptyString, errors.HostStringIsInvalidService
  81. }
  82. func (s String) isDomainVariable() (String, error) {
  83. if err := s.isValid(); err != nil {
  84. log.Traceln(errors.HostString, errors.HostStringIsInvalidDomain)
  85. return EmptyString, errors.HostStringIsInvalidDomain
  86. }
  87. if s == "local" {
  88. return s, nil
  89. }
  90. if _, err := net.LookupHost(s.String()); err != nil {
  91. log.Traceln(errors.HostString, err)
  92. return EmptyString, errors.HostStringIsInvalidDomain
  93. }
  94. return s, nil
  95. }
  96. func (s String) isHostnameVariable() (String, error) {
  97. if err := s.isValid(); err != nil {
  98. log.Traceln(errors.HostString, errors.HostStringIsInvalidHostname, err)
  99. return EmptyString, errors.HostStringIsInvalidHostname
  100. }
  101. if hostname, err := os.Hostname(); err == nil {
  102. if s.String() == hostname {
  103. return s, nil
  104. }
  105. }
  106. if _, err := net.LookupHost(s.String()); err != nil {
  107. log.Traceln(errors.HostString, err)
  108. return EmptyString, errors.HostStringIsInvalidHostname
  109. }
  110. return s, nil
  111. }
  112. func (s String) isTxtVariable() (String, error) {
  113. log.Traceln(errors.HostString, s)
  114. if s.isEmpty() || s[:1][0] == '=' {
  115. return EmptyString, nil
  116. }
  117. if len(s) > 200 {
  118. return EmptyString, errors.HostTXTExceedsLimit
  119. }
  120. // RFC 6763: Spaces in key is significant, can include any character(incl. '=') in value
  121. re := regexp.MustCompile(`^([\x20-\x3C\x3E-\x7E]+)(?:(\=+)?([\x20-\x7E]+)?)$`)
  122. if rs := re.FindAllStringSubmatch(s.String(), 1); len(rs) != 0 {
  123. return String(fmt.Sprintf("%s%s%s", strings.Trim(rs[0][1], " "), rs[0][2], rs[0][3])), nil
  124. }
  125. return EmptyString, nil
  126. }
  127. func (s String) EncodedInstance() string {
  128. // RFC 6763, 4.3: Must be escaped, except leading _
  129. return strings.Replace(regexp.QuoteMeta(s.String()), "_", `\_`, -1)
  130. }
  131. func (s String) Types() []string {
  132. return strings.Split(s.String(), ".")
  133. }
  134. func (s String) RootType() string {
  135. t := s.Types()
  136. return t[len(t)-1]
  137. }