1package dns
2
3// Holds a bunch of helper functions for dealing with labels.
4
5// SplitDomainName splits a name string into it's labels.
6// www.miek.nl. returns []string{"www", "miek", "nl"}
7// .www.miek.nl. returns []string{"", "www", "miek", "nl"},
8// The root label (.) returns nil. Note that using
9// strings.Split(s) will work in most cases, but does not handle
10// escaped dots (\.) for instance.
11// s must be a syntactically valid domain name, see IsDomainName.
12func SplitDomainName(s string) (labels []string) {
13	if s == "" {
14		return nil
15	}
16	fqdnEnd := 0 // offset of the final '.' or the length of the name
17	idx := Split(s)
18	begin := 0
19	if IsFqdn(s) {
20		fqdnEnd = len(s) - 1
21	} else {
22		fqdnEnd = len(s)
23	}
24
25	switch len(idx) {
26	case 0:
27		return nil
28	case 1:
29		// no-op
30	default:
31		for _, end := range idx[1:] {
32			labels = append(labels, s[begin:end-1])
33			begin = end
34		}
35	}
36
37	return append(labels, s[begin:fqdnEnd])
38}
39
40// CompareDomainName compares the names s1 and s2 and
41// returns how many labels they have in common starting from the *right*.
42// The comparison stops at the first inequality. The names are downcased
43// before the comparison.
44//
45// www.miek.nl. and miek.nl. have two labels in common: miek and nl
46// www.miek.nl. and www.bla.nl. have one label in common: nl
47//
48// s1 and s2 must be syntactically valid domain names.
49func CompareDomainName(s1, s2 string) (n int) {
50	// the first check: root label
51	if s1 == "." || s2 == "." {
52		return 0
53	}
54
55	l1 := Split(s1)
56	l2 := Split(s2)
57
58	j1 := len(l1) - 1 // end
59	i1 := len(l1) - 2 // start
60	j2 := len(l2) - 1
61	i2 := len(l2) - 2
62	// the second check can be done here: last/only label
63	// before we fall through into the for-loop below
64	if equal(s1[l1[j1]:], s2[l2[j2]:]) {
65		n++
66	} else {
67		return
68	}
69	for {
70		if i1 < 0 || i2 < 0 {
71			break
72		}
73		if equal(s1[l1[i1]:l1[j1]], s2[l2[i2]:l2[j2]]) {
74			n++
75		} else {
76			break
77		}
78		j1--
79		i1--
80		j2--
81		i2--
82	}
83	return
84}
85
86// CountLabel counts the number of labels in the string s.
87// s must be a syntactically valid domain name.
88func CountLabel(s string) (labels int) {
89	if s == "." {
90		return
91	}
92	off := 0
93	end := false
94	for {
95		off, end = NextLabel(s, off)
96		labels++
97		if end {
98			return
99		}
100	}
101}
102
103// Split splits a name s into its label indexes.
104// www.miek.nl. returns []int{0, 4, 9}, www.miek.nl also returns []int{0, 4, 9}.
105// The root name (.) returns nil. Also see SplitDomainName.
106// s must be a syntactically valid domain name.
107func Split(s string) []int {
108	if s == "." {
109		return nil
110	}
111	idx := make([]int, 1, 3)
112	off := 0
113	end := false
114
115	for {
116		off, end = NextLabel(s, off)
117		if end {
118			return idx
119		}
120		idx = append(idx, off)
121	}
122}
123
124// NextLabel returns the index of the start of the next label in the
125// string s starting at offset.
126// The bool end is true when the end of the string has been reached.
127// Also see PrevLabel.
128func NextLabel(s string, offset int) (i int, end bool) {
129	if s == "" {
130		return 0, true
131	}
132	for i = offset; i < len(s)-1; i++ {
133		if s[i] != '.' {
134			continue
135		}
136		j := i - 1
137		for j >= 0 && s[j] == '\\' {
138			j--
139		}
140
141		if (j-i)%2 == 0 {
142			continue
143		}
144
145		return i + 1, false
146	}
147	return i + 1, true
148}
149
150// PrevLabel returns the index of the label when starting from the right and
151// jumping n labels to the left.
152// The bool start is true when the start of the string has been overshot.
153// Also see NextLabel.
154func PrevLabel(s string, n int) (i int, start bool) {
155	if s == "" {
156		return 0, true
157	}
158	if n == 0 {
159		return len(s), false
160	}
161
162	l := len(s) - 1
163	if s[l] == '.' {
164		l--
165	}
166
167	for ; l >= 0 && n > 0; l-- {
168		if s[l] != '.' {
169			continue
170		}
171		j := l - 1
172		for j >= 0 && s[j] == '\\' {
173			j--
174		}
175
176		if (j-l)%2 == 0 {
177			continue
178		}
179
180		n--
181		if n == 0 {
182			return l + 1, false
183		}
184	}
185
186	return 0, n > 1
187}
188
189// equal compares a and b while ignoring case. It returns true when equal otherwise false.
190func equal(a, b string) bool {
191	// might be lifted into API function.
192	la := len(a)
193	lb := len(b)
194	if la != lb {
195		return false
196	}
197
198	for i := la - 1; i >= 0; i-- {
199		ai := a[i]
200		bi := b[i]
201		if ai >= 'A' && ai <= 'Z' {
202			ai |= 'a' - 'A'
203		}
204		if bi >= 'A' && bi <= 'Z' {
205			bi |= 'a' - 'A'
206		}
207		if ai != bi {
208			return false
209		}
210	}
211	return true
212}
213