summaryrefslogtreecommitdiffhomepage
path: root/rfc1035label/label.go
blob: f727ec6eba9b796e6b13a108b591e140962fac8f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package rfc1035label

import (
	"errors"
	"fmt"
	"strings"
)

// Labels represents RFC1035 labels
//
// This implements RFC 1035 labels, including compression.
// https://tools.ietf.org/html/rfc1035#section-4.1.4
type Labels struct {
	// original contains the original bytes if the object was parsed from a byte
	// sequence, or nil otherwise. The `original` field is necessary to deal
	// with compressed labels. If the labels are further modified, the original
	// content is invalidated and no compression will be used.
	original []byte
	// Labels contains the parsed labels. A change here invalidates the
	// `original` object.
	Labels []string
}

// same compares two string arrays
func same(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	for i := 0; i < len(a); i++ {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

// String prints labels.
func (l *Labels) String() string {
	return fmt.Sprintf("%v", l.Labels)
}

// ToBytes returns a byte sequence representing the labels. If the original
// sequence is modified, the labels are parsed again, otherwise the original
// byte sequence is returned.
func (l *Labels) ToBytes() []byte {
	// if the original byte sequence has been modified, invalidate it and
	// serialize again.
	// NOTE: this function is not thread-safe. If multiple threads modify
	// the `Labels` field, the result may be wrong.
	originalLabels, err := labelsFromBytes(l.original)
	// if the original object has not been modified, or we cannot parse it,
	// return the original bytes.
	if err != nil || (l.original != nil && same(originalLabels, l.Labels)) {
		return l.original
	}
	return labelsToBytes(l.Labels)
}

// Length returns the length in bytes of the serialized labels
func (l *Labels) Length() int {
	return len(l.ToBytes())
}

// NewLabels returns an initialized Labels object.
func NewLabels() *Labels {
	return &Labels{
		Labels: make([]string, 0),
	}
}

// FromBytes reads labels from a bytes stream according to RFC 1035.
func (l *Labels) FromBytes(data []byte) error {
	labs, err := labelsFromBytes(data)
	if err != nil {
		return err
	}
	l.original = data
	l.Labels = labs
	return nil
}

// FromBytes returns a Labels object from the given byte sequence, or an error if
// any.
func FromBytes(data []byte) (*Labels, error) {
	var l Labels
	if err := l.FromBytes(data); err != nil {
		return nil, err
	}
	return &l, nil
}

// ErrBufferTooShort is returned when the label cannot be parsed due to a wrong
// length or missing bytes.
var ErrBufferTooShort = errors.New("rfc1035label: buffer too short")

// fromBytes decodes a serialized stream and returns a list of labels
func labelsFromBytes(buf []byte) ([]string, error) {
	var (
		labels          = make([]string, 0)
		pos, oldPos     int
		label           string
		handlingPointer bool
	)

	for {
		if pos >= len(buf) {
			// interpret label without trailing zero-length byte as a partial
			// domain name field as per RFC 4704 Section 4.2
			if label != "" {
				labels = append(labels, label)
			}

			break
		}
		length := int(buf[pos])
		pos++
		var chunk string
		if length == 0 {
			labels = append(labels, label)
			label = ""
			if handlingPointer {
				pos = oldPos
				handlingPointer = false
			}
		} else if length&0xc0 == 0xc0 {
			// compression pointer
			if handlingPointer {
				return nil, errors.New("rfc1035label: cannot handle nested pointers")
			}
			handlingPointer = true
			if pos+1 > len(buf) {
				return nil, errors.New("rfc1035label: pointer buffer too short")
			}
			off := int(buf[pos-1]&^0xc0)<<8 + int(buf[pos])
			oldPos = pos + 1
			pos = off
		} else {
			if pos+length > len(buf) {
				return nil, ErrBufferTooShort
			}
			chunk = string(buf[pos : pos+length])
			if label != "" {
				label += "."
			}
			label += chunk
			pos += length
		}
	}
	return labels, nil
}

// labelToBytes encodes a label and returns a serialized stream of bytes
func labelToBytes(label string) []byte {
	var encodedLabel []byte
	if len(label) == 0 {
		return []byte{0}
	}
	for _, part := range strings.Split(label, ".") {
		encodedLabel = append(encodedLabel, byte(len(part)))
		encodedLabel = append(encodedLabel, []byte(part)...)
	}
	return append(encodedLabel, 0)
}

// labelsToBytes encodes a list of labels and returns a serialized stream of
// bytes
func labelsToBytes(labels []string) []byte {
	var encodedLabels []byte
	for _, label := range labels {
		encodedLabels = append(encodedLabels, labelToBytes(label)...)
	}
	return encodedLabels
}