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 len(s) == 0 {
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 s[len(s)-1] == '.' {
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		end := 0
32		for i := 1; i < len(idx); i++ {
33			end = idx[i]
34			labels = append(labels, s[begin:end-1])
35			begin = end
36		}
37	}
38
39	labels = append(labels, s[begin:fqdnEnd])
40	return labels
41}
42
43// CompareDomainName compares the names s1 and s2 and
44// returns how many labels they have in common starting from the *right*.
45// The comparison stops at the first inequality. The names are downcased
46// before the comparison.
47//
48// www.miek.nl. and miek.nl. have two labels in common: miek and nl
49// www.miek.nl. and www.bla.nl. have one label in common: nl
50//
51// s1 and s2 must be syntactically valid domain names.
52func CompareDomainName(s1, s2 string) (n int) {
53	// the first check: root label
54	if s1 == "." || s2 == "." {
55		return 0
56	}
57
58	l1 := Split(s1)
59	l2 := Split(s2)
60
61	j1 := len(l1) - 1 // end
62	i1 := len(l1) - 2 // start
63	j2 := len(l2) - 1
64	i2 := len(l2) - 2
65	// the second check can be done here: last/only label
66	// before we fall through into the for-loop below
67	if equal(s1[l1[j1]:], s2[l2[j2]:]) {
68		n++
69	} else {
70		return
71	}
72	for {
73		if i1 < 0 || i2 < 0 {
74			break
75		}
76		if equal(s1[l1[i1]:l1[j1]], s2[l2[i2]:l2[j2]]) {
77			n++
78		} else {
79			break
80		}
81		j1--
82		i1--
83		j2--
84		i2--
85	}
86	return
87}
88
89// CountLabel counts the the number of labels in the string s.
90// s must be a syntactically valid domain name.
91func CountLabel(s string) (labels int) {
92	if s == "." {
93		return
94	}
95	off := 0
96	end := false
97	for {
98		off, end = NextLabel(s, off)
99		labels++
100		if end {
101			return
102		}
103	}
104}
105
106// Split splits a name s into its label indexes.
107// www.miek.nl. returns []int{0, 4, 9}, www.miek.nl also returns []int{0, 4, 9}.
108// The root name (.) returns nil. Also see SplitDomainName.
109// s must be a syntactically valid domain name.
110func Split(s string) []int {
111	if s == "." {
112		return nil
113	}
114	idx := make([]int, 1, 3)
115	off := 0
116	end := false
117
118	for {
119		off, end = NextLabel(s, off)
120		if end {
121			return idx
122		}
123		idx = append(idx, off)
124	}
125}
126
127// NextLabel returns the index of the start of the next label in the
128// string s starting at offset.
129// The bool end is true when the end of the string has been reached.
130// Also see PrevLabel.
131func NextLabel(s string, offset int) (i int, end bool) {
132	quote := false
133	for i = offset; i < len(s)-1; i++ {
134		switch s[i] {
135		case '\\':
136			quote = !quote
137		default:
138			quote = false
139		case '.':
140			if quote {
141				quote = !quote
142				continue
143			}
144			return i + 1, false
145		}
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 n == 0 {
156		return len(s), false
157	}
158	lab := Split(s)
159	if lab == nil {
160		return 0, true
161	}
162	if n > len(lab) {
163		return 0, true
164	}
165	return lab[len(lab)-n], false
166}
167
168// equal compares a and b while ignoring case. It returns true when equal otherwise false.
169func equal(a, b string) bool {
170	// might be lifted into API function.
171	la := len(a)
172	lb := len(b)
173	if la != lb {
174		return false
175	}
176
177	for i := la - 1; i >= 0; i-- {
178		ai := a[i]
179		bi := b[i]
180		if ai >= 'A' && ai <= 'Z' {
181			ai |= ('a' - 'A')
182		}
183		if bi >= 'A' && bi <= 'Z' {
184			bi |= ('a' - 'A')
185		}
186		if ai != bi {
187			return false
188		}
189	}
190	return true
191}
192