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 }