mirror of
https://github.com/coredns/coredns.git
synced 2026-10-09 03:55:21 -04:00
* plugin/file: fix less to follow RFC 1034 and RFC 4034 matching and ordering requirements - Ensure comparison is left-justified - Ensure case folding applies only to A-Z - Decode \DDD without allocations Signed-off-by: Ilya Kulakov <kulakov.ilya@gmail.com> * plugin/file: faster exit for less when a == b Avoid two calls and two reslices. Signed-off-by: Ilya Kulakov <kulakov.ilya@gmail.com> * plugin/file: consolidate less tests Signed-off-by: Ilya Kulakov <kulakov.ilya@gmail.com> * plugin/file: exit less early when there are no more labels Signed-off-by: Ilya Kulakov <kulakov.ilya@gmail.com> * plugin/file: match dns.PackDomainName in handling \-escapes Compare unterminated names as root-terminating Signed-off-by: Ilya Kulakov <kulakov.ilya@gmail.com> * plugin/file: More tests of less. Signed-off-by: Ilya Kulakov <kulakov.ilya@gmail.com> --------- Signed-off-by: Ilya Kulakov <kulakov.ilya@gmail.com>
118 lines
2.6 KiB
Go
118 lines
2.6 KiB
Go
package tree
|
|
|
|
// less returns <0 when a is less than b, 0 when they are equal and >0 when a is larger than b.
|
|
//
|
|
// Follows DNSSEC canonical ordering (RFC 4034, Section 6.1):
|
|
// - `\DDD` byte is decoded before comparison
|
|
// - Uppercase A-Z letters are treated as if they were lowercase
|
|
// - Absence of octet sorts before zero value octet
|
|
//
|
|
// Quirks:
|
|
// - Trailing `\` that escapes nothing is ignored
|
|
// - Leading `\` in `\D` and `\DD` is ignored
|
|
// - Non-FQDN names are assumed to be root-terminated
|
|
func less(a, b string) int {
|
|
var (
|
|
adot, bdot int
|
|
aoff, boff int
|
|
alast, blast = stripTrailingBackslash(a), stripTrailingBackslash(b)
|
|
ac, bc byte
|
|
)
|
|
|
|
if adot, _ = prevDot(a, alast); alast >= 0 && alast == adot {
|
|
alast--
|
|
}
|
|
if bdot, _ = prevDot(b, blast); blast >= 0 && blast == bdot {
|
|
blast--
|
|
}
|
|
|
|
// dot off
|
|
// ▼ ▼
|
|
// my.exampledomain.com.
|
|
// ▲ ▲
|
|
// first last
|
|
|
|
for alast >= 0 && blast >= 0 {
|
|
adot, aoff = prevDot(a, alast)
|
|
bdot, boff = prevDot(b, blast)
|
|
|
|
for aoff <= alast && boff <= blast {
|
|
ac, aoff = a[aoff], aoff+1
|
|
if ac == '\\' {
|
|
ac, aoff = nextEscapedByte(a, aoff, alast)
|
|
}
|
|
ac = foldCase(ac)
|
|
|
|
bc, boff = b[boff], boff+1
|
|
if bc == '\\' {
|
|
bc, boff = nextEscapedByte(b, boff, blast)
|
|
}
|
|
bc = foldCase(bc)
|
|
|
|
if ac != bc {
|
|
return int(ac) - int(bc)
|
|
}
|
|
}
|
|
|
|
// Shorter label means less.
|
|
if d := (alast - aoff) - (blast - boff); d != 0 {
|
|
return d
|
|
}
|
|
|
|
alast = adot - 1
|
|
blast = bdot - 1
|
|
}
|
|
|
|
// Fewer labels means less.
|
|
return alast - blast
|
|
}
|
|
|
|
// stripTrailingBackslash removes hanging backslash that escapes nothing.
|
|
func stripTrailingBackslash(s string) (last int) {
|
|
last = len(s) - 1
|
|
for last >= 0 && s[last] == '\\' {
|
|
last--
|
|
}
|
|
if (len(s)-last)%2 == 0 { // `...\` vs `...\\`
|
|
return len(s) - 2
|
|
}
|
|
return len(s) - 1
|
|
}
|
|
|
|
// prevDot finds label-separator dot in [0, last].
|
|
func prevDot(s string, last int) (dot, first int) {
|
|
for last >= 0 {
|
|
if s[last] != '.' {
|
|
last--
|
|
continue
|
|
}
|
|
off1 := last - 1
|
|
for off1 >= 0 && s[off1] == '\\' {
|
|
off1--
|
|
}
|
|
if (last-off1)%2 != 0 { // `a\.example` vs `a\\.example`
|
|
break
|
|
}
|
|
last = off1
|
|
}
|
|
return last, last + 1
|
|
}
|
|
|
|
// nextByte implements \DDD-aware and escape-aware advancement.
|
|
func nextEscapedByte(s string, off, last int) (byte, int) {
|
|
if off+2 <= last {
|
|
d0, d1, d2 := s[off]-'0', s[off+1]-'0', s[off+2]-'0'
|
|
if d0 < 10 && d1 < 10 && d2 < 10 {
|
|
return d0*100 + d1*10 + d2, off + 3
|
|
}
|
|
}
|
|
return s[off], off + 1
|
|
}
|
|
|
|
func foldCase(c byte) byte {
|
|
if c-'A' < 26 {
|
|
c |= 0x20
|
|
}
|
|
return c
|
|
}
|