aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/dbus/address.go
blob: 4afea9e1172e74d1b32f91f6b91bd34212ad8a2c (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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package dbus

import (
	"bytes"
	"encoding/hex"
	"errors"
	"fmt"
	"slices"
)

type AddrEntry struct {
	Method string      `json:"method"`
	Values [][2]string `json:"values"`
}

// EqualAddrEntries returns whether two slices of [AddrEntry] are equal.
func EqualAddrEntries(entries, target []AddrEntry) bool {
	return slices.EqualFunc(entries, target, func(a AddrEntry, b AddrEntry) bool {
		return a.Method == b.Method && slices.Equal(a.Values, b.Values)
	})
}

// Parse parses D-Bus address according to
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses
func Parse(addr []byte) ([]AddrEntry, error) {
	// Look for a semicolon
	address := bytes.Split(bytes.TrimSuffix(addr, []byte{';'}), []byte{';'})

	// Allocate for entries
	v := make([]AddrEntry, len(address))

	for i, s := range address {
		var pairs [][]byte

		// Look for the colon :
		if method, list, ok := bytes.Cut(s, []byte{':'}); !ok {
			return v, &BadAddressError{ErrNoColon, i, s, -1, nil}
		} else {
			pairs = bytes.Split(list, []byte{','})
			v[i].Method = string(method)
			v[i].Values = make([][2]string, len(pairs))
		}

		for j, pair := range pairs {
			key, value, ok := bytes.Cut(pair, []byte{'='})
			if !ok {
				return v, &BadAddressError{ErrBadPairSep, i, s, j, pair}
			}
			if len(key) == 0 {
				return v, &BadAddressError{ErrBadPairKey, i, s, j, pair}
			}
			if len(value) == 0 {
				return v, &BadAddressError{ErrBadPairVal, i, s, j, pair}
			}
			v[i].Values[j][0] = string(key)

			if val, errno := unescapeValue(value); errno != errSuccess {
				return v, &BadAddressError{errno, i, s, j, pair}
			} else {
				v[i].Values[j][1] = string(val)
			}
		}
	}

	return v, nil
}

func unescapeValue(v []byte) (val []byte, errno ParseError) {
	if l := len(v) - (bytes.Count(v, []byte{'%'}) * 2); l < 0 {
		errno = ErrBadValLength
		return
	} else {
		val = make([]byte, l)
	}

	var i, skip int
	for iu, b := range v {
		if skip > 0 {
			skip--
			continue
		}

		if found := bytes.Contains([]byte("-_/.\\*"), []byte{b}); found { // - // _/.\*
			goto opt
		} else if b >= '0' && b <= '9' { // 0-9
			goto opt
		} else if b >= 'A' && b <= 'Z' { // A-Z
			goto opt
		} else if b >= 'a' && b <= 'z' { // a-z
			goto opt
		}

		if b != '%' {
			errno = ErrBadValByte
			break
		}

		skip += 2
		if iu+2 >= len(v) {
			errno = ErrBadValHexLength
			break
		}
		if c, err := hex.Decode(val[i:i+1], v[iu+1:iu+3]); err != nil {
			if _, ok := errors.AsType[hex.InvalidByteError](err); ok {
				errno = ErrBadValHexByte
				break
			}
			// unreachable
			panic(err.Error())
		} else if c != 1 {
			// unreachable
			panic(fmt.Sprintf("invalid decode length %d", c))
		}
		i++
		continue

	opt:
		val[i] = b
		i++
	}

	return
}

type ParseError uint8

func (e ParseError) Error() string {
	switch e {
	case errSuccess:
		panic("attempted to return success as error")
	case ErrNoColon:
		return "address does not contain a colon"
	case ErrBadPairSep:
		return "'=' character not found"
	case ErrBadPairKey:
		return "'=' character has no key preceding it"
	case ErrBadPairVal:
		return "'=' character has no value following it"
	case ErrBadValLength:
		return "unescaped value has impossible length"
	case ErrBadValByte:
		return "in D-Bus address, characters other than [-0-9A-Za-z_/.\\*] should have been escaped"
	case ErrBadValHexLength:
		return "in D-Bus address, percent character was not followed by two hex digits"
	case ErrBadValHexByte:
		return "in D-Bus address, percent character was followed by characters other than hex digits"

	default:
		return fmt.Sprintf("parse error %d", e)
	}
}

const (
	errSuccess ParseError = iota
	ErrNoColon
	ErrBadPairSep
	ErrBadPairKey
	ErrBadPairVal
	ErrBadValLength
	ErrBadValByte
	ErrBadValHexLength
	ErrBadValHexByte
)

type BadAddressError struct {
	// error type
	Type ParseError

	// bad entry position
	EntryPos int
	// bad entry value
	EntryVal []byte

	// bad pair position
	PairPos int
	// bad pair value
	PairVal []byte
}

func (a *BadAddressError) Is(err error) bool {
	var b *BadAddressError
	return errors.As(err, &b) && a.Type == b.Type &&
		a.EntryPos == b.EntryPos && slices.Equal(a.EntryVal, b.EntryVal) &&
		a.PairPos == b.PairPos && slices.Equal(a.PairVal, b.PairVal)
}

func (a *BadAddressError) Error() string {
	return a.Type.Error()
}

func (a *BadAddressError) Unwrap() error {
	return a.Type
}