blob: 9769d7ec3d3a9366b934383f736ebeb0a61cb002 [file] [log] [blame]
package dns
import (
"net/netip"
"strings"
)
// IsSubDomain returns true if child is the same as or a subdomain of parent.
// Both names should be in canonical form.
func IsSubDomain(parent, child string) bool {
offset := len(child) - len(parent)
if offset < 0 || child[offset:] != parent {
return false
}
if offset == 0 || parent == "." {
return true
}
if child[offset-1] != '.' {
return false
}
j := offset - 2
for j >= 0 && child[j] == '\\' {
j--
}
return (offset-j)%2 == 0
}
// SplitLastLabel splits off the last label of a domain name. For example,
// "www.example.com." is split into "www.example." and "com".
func SplitLastLabel(name string) (rest string, label string) {
labelEnd := len(name)
if labelEnd != 0 && name[labelEnd-1] == '.' {
labelEnd--
}
labelStart := labelEnd
for ; labelStart > 0; labelStart-- {
if name[labelStart-1] != '.' {
continue
}
j := labelStart - 2
for j >= 0 && name[j] == '\\' {
j--
}
if (labelStart-j)%2 != 0 {
continue
}
break
}
return name[:labelStart], name[labelStart:labelEnd]
}
// ParseReverse parses name as a reverse lookup name. If name is not a reverse
// name, the returned IP is invalid. The second return value indicates how many
// bits of the address are present. The third return value is true if there are
// extra labels before the reverse name.
func ParseReverse(name string) (ip netip.Addr, bits int, extra bool) {
if strings.HasSuffix(name, "in-addr.arpa.") {
var ip [4]uint8
field := 0
pos := len(name) - len("in-addr.arpa.") - 1
for pos >= 0 && field < 4 {
if name[pos] != '.' {
break
}
nextPos := pos - 1
for nextPos >= 0 && name[nextPos] >= '0' && name[nextPos] <= '9' {
nextPos--
}
val := 0
for valPos := nextPos + 1; valPos < pos; valPos++ {
val = val*10 + int(name[valPos]) - '0'
}
valLen := pos - nextPos - 1
if valLen == 0 || valLen > 3 || (valLen != 1 && name[nextPos+1] == '0') || val > 255 {
// Number is empty, or too long, or has leading zero, or is too large.
break
}
ip[field] = uint8(val)
field++
pos = nextPos
}
if pos >= 0 {
// We did not parse the entire name.
j := pos - 1
for j >= 0 && name[j] == '\\' {
j--
}
if name[pos] != '.' || (pos-j)%2 == 0 {
// The last label we parsed was not terminated by a non-escaped dot.
field--
if field < 0 {
return netip.Addr{}, 0, false
}
ip[field] = 0
}
}
return netip.AddrFrom4(ip), field * 8, pos >= 0
}
if strings.HasSuffix(name, "ip6.arpa.") {
var ip [16]uint8
field := 0
half := false
pos := len(name) - len("ip6.arpa.") - 1
for pos > 0 && field < 16 {
if name[pos] != '.' {
break
}
var nibble uint8
if name[pos-1] >= '0' && name[pos-1] <= '9' {
nibble = name[pos-1] - '0'
} else if name[pos-1] >= 'a' && name[pos-1] <= 'f' {
nibble = name[pos-1] - 'a' + 10
} else {
break
}
if half {
ip[field] |= nibble
field++
half = false
} else {
ip[field] = nibble << 4
half = true
}
pos -= 2
}
if pos >= 0 {
// We did not parse the entire name.
j := pos - 1
for j >= 0 && name[j] == '\\' {
j--
}
if name[pos] != '.' || (pos-j)%2 == 0 {
// The last label we parsed was not terminated by a non-escaped dot.
if half {
half = false
ip[field] = 0
} else {
half = true
field--
if field < 0 {
return netip.Addr{}, 0, false
}
ip[field] &= 0xf0
}
}
}
bits := field * 8
if half {
bits += 4
}
return netip.AddrFrom16(ip), bits, pos >= 0
}
return netip.Addr{}, 0, false
}