Ophestra
1651eb06df
This parses D-Bus addresses according to spec. It does significantly fewer copies than dbus_parse_address. Signed-off-by: Ophestra <cat@gensokyo.uk>
187 lines
4.0 KiB
Go
187 lines
4.0 KiB
Go
package dbus
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
)
|
|
|
|
type AddrEntry struct {
|
|
Method string `json:"method"`
|
|
Values [][2]string `json:"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
|
|
}
|