All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m18s
Test / Hakurei (push) Successful in 3m17s
Test / Sandbox (race detector) (push) Successful in 4m7s
Test / Hpkg (push) Successful in 4m13s
Test / Hakurei (race detector) (push) Successful in 5m3s
Test / Flake checks (push) Successful in 1m40s
These packages are highly specific to hakurei and are difficult to use safely from other pieces of code. Their exported symbols are made available until v0.4.0 where they will be removed for #24. Signed-off-by: Ophestra <cat@gensokyo.uk>
194 lines
4.2 KiB
Go
194 lines
4.2 KiB
Go
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 ib := bytes.IndexByte([]byte("-_/.\\*"), b); ib != -1 { // - // _/.\*
|
|
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 errors.As(err, new(hex.InvalidByteError)) {
|
|
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
|
|
}
|