220 lines
4.6 KiB
Go
220 lines
4.6 KiB
Go
package dns
|
|
|
|
// Holds a bunch of helper functions for dealing with labels.
|
|
|
|
// SplitDomainName splits a name string into it's labels.
|
|
// www.miek.nl. returns []string{"www", "miek", "nl"}
|
|
// .www.miek.nl. returns []string{"", "www", "miek", "nl"},
|
|
// The root label (.) returns nil. Note that using
|
|
// strings.Split(s) will work in most cases, but does not handle
|
|
// escaped dots (\.) for instance.
|
|
// s must be a syntactically valid domain name, see IsDomainName.
|
|
func SplitDomainName(s string) (labels []string) {
|
|
if len(s) == 0 {
|
|
return nil
|
|
}
|
|
if s == "." {
|
|
return nil
|
|
}
|
|
// offset of the final '.' or the length of the name
|
|
var fqdnEnd int
|
|
if IsFqdn(s) {
|
|
fqdnEnd = len(s) - 1
|
|
} else {
|
|
fqdnEnd = len(s)
|
|
}
|
|
var (
|
|
begin int
|
|
off int
|
|
end bool
|
|
)
|
|
for {
|
|
off, end = NextLabel(s, off)
|
|
if end {
|
|
break
|
|
}
|
|
labels = append(labels, s[begin:off-1])
|
|
begin = off
|
|
}
|
|
return append(labels, s[begin:fqdnEnd])
|
|
}
|
|
|
|
// CompareDomainName compares the names s1 and s2 and
|
|
// returns how many labels they have in common starting from the *right*.
|
|
// The comparison stops at the first inequality. The names are downcased
|
|
// before the comparison.
|
|
//
|
|
// www.miek.nl. and miek.nl. have two labels in common: miek and nl
|
|
// www.miek.nl. and www.bla.nl. have one label in common: nl
|
|
//
|
|
// s1 and s2 must be syntactically valid domain names.
|
|
func CompareDomainName(s1, s2 string) (n int) {
|
|
// the first check: root label
|
|
if s1 == "." || s2 == "." {
|
|
return 0
|
|
}
|
|
|
|
j1 := len(s1)
|
|
if s1[j1-1] == '.' {
|
|
j1--
|
|
}
|
|
j2 := len(s2)
|
|
if s2[j2-1] == '.' {
|
|
j2--
|
|
}
|
|
var i1, i2 int
|
|
for {
|
|
i1 = prevLabel(s1, j1-1)
|
|
i2 = prevLabel(s2, j2-1)
|
|
if equal(s1[i1:j1], s2[i2:j2]) {
|
|
n++
|
|
} else {
|
|
break
|
|
}
|
|
if i1 == 0 || i2 == 0 {
|
|
break
|
|
}
|
|
j1 = i1 - 2
|
|
j2 = i2 - 2
|
|
}
|
|
return
|
|
}
|
|
|
|
// CountLabel counts the the number of labels in the string s.
|
|
// s must be a syntactically valid domain name.
|
|
func CountLabel(s string) int {
|
|
if s == "." {
|
|
return 0
|
|
}
|
|
labels := 1
|
|
for i := 0; i < len(s)-1; i++ {
|
|
c := s[i]
|
|
if c == '\\' {
|
|
i++
|
|
continue
|
|
}
|
|
if c == '.' {
|
|
labels++
|
|
}
|
|
}
|
|
return labels
|
|
}
|
|
|
|
// Split splits a name s into its label indexes.
|
|
// www.miek.nl. returns []int{0, 4, 9}, www.miek.nl also returns []int{0, 4, 9}.
|
|
// The root name (.) returns nil. Also see SplitDomainName.
|
|
// s must be a syntactically valid domain name.
|
|
func Split(s string) []int {
|
|
if s == "." {
|
|
return nil
|
|
}
|
|
idx := make([]int, 1, 3)
|
|
off := 0
|
|
end := false
|
|
|
|
for {
|
|
off, end = NextLabel(s, off)
|
|
if end {
|
|
return idx
|
|
}
|
|
idx = append(idx, off)
|
|
}
|
|
}
|
|
|
|
// NextLabel returns the index of the start of the next label in the
|
|
// string s starting at offset.
|
|
// The bool end is true when the end of the string has been reached.
|
|
// Also see PrevLabel.
|
|
func NextLabel(s string, offset int) (i int, end bool) {
|
|
for i = offset; i < len(s)-1; i++ {
|
|
c := s[i]
|
|
if c == '\\' {
|
|
i++
|
|
continue
|
|
}
|
|
if c == '.' {
|
|
return i + 1, false
|
|
}
|
|
}
|
|
return i + 1, true
|
|
}
|
|
|
|
func prevLabel(s string, offset int) int {
|
|
for i := offset; i >= 0; i-- {
|
|
if s[i] == '.' {
|
|
if i == 0 || s[i-1] != '\\' {
|
|
return i + 1 // the '.' is not escaped
|
|
}
|
|
// We are at '\.' and need to check if the '\' itself is escaped.
|
|
// We do this by walking backwards from '\.' and counting the
|
|
// number of '\' we encounter. If the number of '\' is even
|
|
// (though here it's actually odd since we start at '\.') the '\'
|
|
// is escaped.
|
|
j := i - 2
|
|
for ; j >= 0 && s[j] == '\\'; j-- {
|
|
}
|
|
// An odd number here indicates that the '\' preceding the '.'
|
|
// is escaped.
|
|
if (i-j)&1 == 1 {
|
|
return i + 1
|
|
}
|
|
i = j + 1
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// PrevLabel returns the index of the label when starting from the right and
|
|
// jumping n labels to the left.
|
|
// The bool start is true when the start of the string has been overshot.
|
|
// Also see NextLabel.
|
|
func PrevLabel(s string, n int) (i int, start bool) {
|
|
if s == "." {
|
|
return 0, true
|
|
}
|
|
if n == 0 {
|
|
return len(s), false
|
|
}
|
|
i = len(s) - 1
|
|
if s[i] == '.' {
|
|
i--
|
|
}
|
|
for ; n > 0; n-- {
|
|
i = prevLabel(s, i)
|
|
if i == 0 {
|
|
break
|
|
}
|
|
i -= 2
|
|
}
|
|
if n > 0 {
|
|
return 0, true
|
|
}
|
|
return i + 2, false
|
|
}
|
|
|
|
// equal compares a and b while ignoring case. It returns true when equal otherwise false.
|
|
func equal(a, b string) bool {
|
|
// might be lifted into API function.
|
|
la := len(a)
|
|
lb := len(b)
|
|
if la != lb {
|
|
return false
|
|
}
|
|
if a != b {
|
|
// case-insensitive comparison
|
|
for i := la - 1; i >= 0; i-- {
|
|
ai := a[i]
|
|
bi := b[i]
|
|
if ai != bi {
|
|
if bi < ai {
|
|
bi, ai = ai, bi
|
|
}
|
|
if !('A' <= ai && ai <= 'Z' && bi == ai+'a'-'A') {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|