1 package 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.
12 func 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.
49 func 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.
88 func 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.
107 func 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. A negative offset will cause a panic.
126 // The bool end is true when the end of the string has been reached.
127 // Also see PrevLabel.
128 func 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.
154 func 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.
190 func 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