labels.go raw

   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