internal: relocate packages
All checks were successful
Test / Create distribution (push) Successful in 36s
Test / Sandbox (push) Successful in 2m20s
Test / Hakurei (push) Successful in 3m19s
Test / Hpkg (push) Successful in 4m12s
Test / Sandbox (race detector) (push) Successful in 4m31s
Test / Hakurei (race detector) (push) Successful in 5m12s
Test / Flake checks (push) Successful in 1m32s
All checks were successful
Test / Create distribution (push) Successful in 36s
Test / Sandbox (push) Successful in 2m20s
Test / Hakurei (push) Successful in 3m19s
Test / Hpkg (push) Successful in 4m12s
Test / Sandbox (race detector) (push) Successful in 4m31s
Test / Hakurei (race detector) (push) Successful in 5m12s
Test / Flake checks (push) Successful in 1m32s
Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
193
internal/dbus/address.go
Normal file
193
internal/dbus/address.go
Normal file
@@ -0,0 +1,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 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
|
||||
}
|
||||
59
internal/dbus/address_escape_test.go
Normal file
59
internal/dbus/address_escape_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnescapeValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
value string
|
||||
want string
|
||||
wantErr ParseError
|
||||
}{
|
||||
// upstream test cases
|
||||
{value: "abcde", want: "abcde"},
|
||||
{value: "", want: ""},
|
||||
{value: "%20%20", want: " "},
|
||||
{value: "%24", want: "$"},
|
||||
{value: "%25", want: "%"},
|
||||
{value: "abc%24", want: "abc$"},
|
||||
{value: "%24abc", want: "$abc"},
|
||||
{value: "abc%24abc", want: "abc$abc"},
|
||||
{value: "/", want: "/"},
|
||||
{value: "-", want: "-"},
|
||||
{value: "_", want: "_"},
|
||||
{value: "A", want: "A"},
|
||||
{value: "I", want: "I"},
|
||||
{value: "Z", want: "Z"},
|
||||
{value: "a", want: "a"},
|
||||
{value: "i", want: "i"},
|
||||
{value: "z", want: "z"},
|
||||
/* Bug: https://bugs.freedesktop.org/show_bug.cgi?id=53499 */
|
||||
{value: "%c3%b6", want: "\xc3\xb6"},
|
||||
|
||||
{value: "%a", wantErr: ErrBadValHexLength},
|
||||
{value: "%q", wantErr: ErrBadValHexLength},
|
||||
{value: "%az", wantErr: ErrBadValHexByte},
|
||||
{value: "%%", wantErr: ErrBadValLength},
|
||||
{value: "%$$", wantErr: ErrBadValHexByte},
|
||||
{value: "abc%a", wantErr: ErrBadValHexLength},
|
||||
{value: "%axyz", wantErr: ErrBadValHexByte},
|
||||
{value: "%", wantErr: ErrBadValLength},
|
||||
{value: "$", wantErr: ErrBadValByte},
|
||||
{value: " ", wantErr: ErrBadValByte},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run("unescape "+tc.value, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got, errno := unescapeValue([]byte(tc.value)); errno != tc.wantErr {
|
||||
t.Errorf("unescapeValue() errno = %v, wantErr %v", errno, tc.wantErr)
|
||||
} else if tc.wantErr == errSuccess && string(got) != tc.want {
|
||||
t.Errorf("unescapeValue() = %q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
123
internal/dbus/address_test.go
Normal file
123
internal/dbus/address_test.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package dbus_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/internal/dbus"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
addr string
|
||||
want []dbus.AddrEntry
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "simple session unix",
|
||||
addr: "unix:path=/run/user/1971/bus",
|
||||
want: []dbus.AddrEntry{{
|
||||
Method: "unix",
|
||||
Values: [][2]string{{"path", "/run/user/1971/bus"}},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "simple upper escape",
|
||||
addr: "debug:name=Test,cat=cute,escaped=%c3%b6",
|
||||
want: []dbus.AddrEntry{{
|
||||
Method: "debug",
|
||||
Values: [][2]string{
|
||||
{"name", "Test"},
|
||||
{"cat", "cute"},
|
||||
{"escaped", "\xc3\xb6"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "simple bad escape",
|
||||
addr: "debug:name=%",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadValLength,
|
||||
EntryPos: 0, EntryVal: []byte("debug:name=%"), PairPos: 0, PairVal: []byte("name=%")},
|
||||
},
|
||||
|
||||
// upstream test cases
|
||||
{
|
||||
name: "full address success",
|
||||
addr: "unix:path=/tmp/foo;debug:name=test,sliff=sloff;",
|
||||
want: []dbus.AddrEntry{
|
||||
{Method: "unix", Values: [][2]string{{"path", "/tmp/foo"}}},
|
||||
{Method: "debug", Values: [][2]string{{"name", "test"}, {"sliff", "sloff"}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty address",
|
||||
addr: "",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrNoColon,
|
||||
EntryVal: []byte{}, PairPos: -1},
|
||||
},
|
||||
{
|
||||
name: "no body",
|
||||
addr: "foo",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrNoColon,
|
||||
EntryPos: 0, EntryVal: []byte("foo"), PairPos: -1},
|
||||
},
|
||||
{
|
||||
name: "no pair separator",
|
||||
addr: "foo:bar",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
|
||||
EntryPos: 0, EntryVal: []byte("foo:bar"), PairPos: 0, PairVal: []byte("bar")},
|
||||
},
|
||||
{
|
||||
name: "no pair separator multi pair",
|
||||
addr: "foo:bar,baz",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
|
||||
EntryPos: 0, EntryVal: []byte("foo:bar,baz"), PairPos: 0, PairVal: []byte("bar")},
|
||||
},
|
||||
{
|
||||
name: "no pair separator single valid pair",
|
||||
addr: "foo:bar=foo,baz",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
|
||||
EntryPos: 0, EntryVal: []byte("foo:bar=foo,baz"), PairPos: 1, PairVal: []byte("baz")},
|
||||
},
|
||||
{
|
||||
name: "no body single valid address",
|
||||
addr: "foo:bar=foo;baz",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrNoColon,
|
||||
EntryPos: 1, EntryVal: []byte("baz"), PairPos: -1},
|
||||
},
|
||||
{
|
||||
name: "no key",
|
||||
addr: "foo:=foo",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairKey,
|
||||
EntryPos: 0, EntryVal: []byte("foo:=foo"), PairPos: 0, PairVal: []byte("=foo")},
|
||||
},
|
||||
{
|
||||
name: "no value",
|
||||
addr: "foo:foo=",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairVal,
|
||||
EntryPos: 0, EntryVal: []byte("foo:foo="), PairPos: 0, PairVal: []byte("foo=")},
|
||||
},
|
||||
{
|
||||
name: "no pair separator single valid pair trailing",
|
||||
addr: "foo:foo,bar=baz",
|
||||
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
|
||||
EntryPos: 0, EntryVal: []byte("foo:foo,bar=baz"), PairPos: 0, PairVal: []byte("foo")},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got, err := dbus.Parse([]byte(tc.addr)); !errors.Is(err, tc.wantErr) {
|
||||
t.Errorf("Parse() error = %v, wantErr %v", err, tc.wantErr)
|
||||
} else if tc.wantErr == nil && !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("Parse() = %#v, want %#v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
71
internal/dbus/config.go
Normal file
71
internal/dbus/config.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
// ProxyPair is an upstream dbus address and a downstream socket path.
|
||||
type ProxyPair [2]string
|
||||
|
||||
// Args returns the xdg-dbus-proxy arguments equivalent of [hst.BusConfig].
|
||||
func Args(c *hst.BusConfig, bus ProxyPair) (args []string) {
|
||||
argc := 2 + len(c.See) + len(c.Talk) + len(c.Own) + len(c.Call) + len(c.Broadcast)
|
||||
if c.Log {
|
||||
argc++
|
||||
}
|
||||
if c.Filter {
|
||||
argc++
|
||||
}
|
||||
|
||||
args = make([]string, 0, argc)
|
||||
args = append(args, bus[0], bus[1])
|
||||
if c.Filter {
|
||||
args = append(args, "--filter")
|
||||
}
|
||||
for _, name := range c.See {
|
||||
args = append(args, "--see="+name)
|
||||
}
|
||||
for _, name := range c.Talk {
|
||||
args = append(args, "--talk="+name)
|
||||
}
|
||||
for _, name := range c.Own {
|
||||
args = append(args, "--own="+name)
|
||||
}
|
||||
for name, rule := range c.Call {
|
||||
args = append(args, "--call="+name+"="+rule)
|
||||
}
|
||||
for name, rule := range c.Broadcast {
|
||||
args = append(args, "--broadcast="+name+"="+rule)
|
||||
}
|
||||
if c.Log {
|
||||
args = append(args, "--log")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NewConfig returns the address of a new [hst.BusConfig] with optional defaults.
|
||||
func NewConfig(id string, defaults, mpris bool) *hst.BusConfig {
|
||||
c := hst.BusConfig{
|
||||
Call: make(map[string]string),
|
||||
Broadcast: make(map[string]string),
|
||||
|
||||
Filter: true,
|
||||
}
|
||||
|
||||
if defaults {
|
||||
c.Talk = []string{"org.freedesktop.DBus", "org.freedesktop.Notifications"}
|
||||
|
||||
c.Call["org.freedesktop.portal.*"] = "*"
|
||||
c.Broadcast["org.freedesktop.portal.*"] = "@/org/freedesktop/portal/*"
|
||||
|
||||
if id != "" {
|
||||
c.Own = []string{id + ".*"}
|
||||
if mpris {
|
||||
c.Own = append(c.Own, "org.mpris.MediaPlayer2."+id+".*")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &c
|
||||
}
|
||||
124
internal/dbus/config_test.go
Normal file
124
internal/dbus/config_test.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package dbus_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/dbus"
|
||||
)
|
||||
|
||||
func TestConfigArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range testCasesExt {
|
||||
if tc.wantErr {
|
||||
// args does not check for nulls
|
||||
continue
|
||||
}
|
||||
|
||||
t.Run("build arguments for "+tc.id, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := dbus.Args(tc.c, tc.bus); !slices.Equal(got, tc.want) {
|
||||
t.Errorf("Args: %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
ids := [...]string{"org.chromium.Chromium", "dev.vencord.Vesktop"}
|
||||
|
||||
type newTestCase struct {
|
||||
id string
|
||||
args [2]bool
|
||||
want *hst.BusConfig
|
||||
}
|
||||
|
||||
// populate tests from IDs in generic tests
|
||||
tcs := make([]newTestCase, 0, (len(ids)+1)*4)
|
||||
// tests for defaults without id
|
||||
tcs = append(tcs,
|
||||
newTestCase{"", [2]bool{false, false}, &hst.BusConfig{
|
||||
Call: make(map[string]string),
|
||||
Broadcast: make(map[string]string),
|
||||
Filter: true,
|
||||
}},
|
||||
newTestCase{"", [2]bool{false, true}, &hst.BusConfig{
|
||||
Call: make(map[string]string),
|
||||
Broadcast: make(map[string]string),
|
||||
Filter: true,
|
||||
}},
|
||||
newTestCase{"", [2]bool{true, false}, &hst.BusConfig{
|
||||
Talk: []string{"org.freedesktop.DBus", "org.freedesktop.Notifications"},
|
||||
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
||||
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
||||
Filter: true,
|
||||
}},
|
||||
newTestCase{"", [2]bool{true, true}, &hst.BusConfig{
|
||||
Talk: []string{"org.freedesktop.DBus", "org.freedesktop.Notifications"},
|
||||
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
||||
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
||||
Filter: true,
|
||||
}},
|
||||
)
|
||||
for _, id := range ids {
|
||||
tcs = append(tcs,
|
||||
newTestCase{id, [2]bool{false, false}, &hst.BusConfig{
|
||||
Call: make(map[string]string),
|
||||
Broadcast: make(map[string]string),
|
||||
Filter: true,
|
||||
}},
|
||||
newTestCase{id, [2]bool{false, true}, &hst.BusConfig{
|
||||
Call: make(map[string]string),
|
||||
Broadcast: make(map[string]string),
|
||||
Filter: true,
|
||||
}},
|
||||
newTestCase{id, [2]bool{true, false}, &hst.BusConfig{
|
||||
Talk: []string{"org.freedesktop.DBus", "org.freedesktop.Notifications"},
|
||||
Own: []string{id + ".*"},
|
||||
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
||||
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
||||
Filter: true,
|
||||
}},
|
||||
newTestCase{id, [2]bool{true, true}, &hst.BusConfig{
|
||||
Talk: []string{"org.freedesktop.DBus", "org.freedesktop.Notifications"},
|
||||
Own: []string{id + ".*", "org.mpris.MediaPlayer2." + id + ".*"},
|
||||
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
||||
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
||||
Filter: true,
|
||||
}},
|
||||
)
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
name := new(strings.Builder)
|
||||
name.WriteString("create new configuration struct")
|
||||
|
||||
if tc.args[0] {
|
||||
name.WriteString(" with builtin defaults")
|
||||
if tc.args[1] {
|
||||
name.WriteString(" (mpris)")
|
||||
}
|
||||
}
|
||||
|
||||
if tc.id != "" {
|
||||
name.WriteString(" for application ID ")
|
||||
name.WriteString(tc.id)
|
||||
}
|
||||
|
||||
t.Run(name.String(), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if gotC := dbus.NewConfig(tc.id, tc.args[0], tc.args[1]); !reflect.DeepEqual(gotC, tc.want) {
|
||||
t.Errorf("NewConfig(%q, %t, %t) = %v, want %v",
|
||||
tc.id, tc.args[0], tc.args[1],
|
||||
gotC, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
69
internal/dbus/dbus.go
Normal file
69
internal/dbus/dbus.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// Package dbus wraps xdg-dbus-proxy and implements configuration and sandboxing of the underlying helper process.
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
/*
|
||||
SessionBusAddress is the name of the environment variable where the address of the login session message bus is given in.
|
||||
|
||||
If that variable is not set, applications may also try to read the address from the X Window System root window property _DBUS_SESSION_BUS_ADDRESS.
|
||||
The root window property must have type STRING. The environment variable should have precedence over the root window property.
|
||||
|
||||
The address of the login session message bus is given in the DBUS_SESSION_BUS_ADDRESS environment variable.
|
||||
If DBUS_SESSION_BUS_ADDRESS is not set, or if it's set to the string "autolaunch:",
|
||||
the system should use platform-specific methods of locating a running D-Bus session server,
|
||||
or starting one if a running instance cannot be found.
|
||||
Note that this mechanism is not recommended for attempting to determine if a daemon is running.
|
||||
It is inherently racy to attempt to make this determination, since the bus daemon may be started just before or just after the determination is made.
|
||||
Therefore, it is recommended that applications do not try to make this determination for their functionality purposes, and instead they should attempt to start the server.
|
||||
|
||||
This package diverges from the specification, as the caller is unlikely to be an X client, or be in a position to autolaunch a dbus server.
|
||||
So a fallback address with a socket located in the well-known default XDG_RUNTIME_DIR formatting is used.
|
||||
*/
|
||||
SessionBusAddress = "DBUS_SESSION_BUS_ADDRESS"
|
||||
|
||||
/*
|
||||
SystemBusAddress is the name of the environment variable where the address of the system message bus is given in.
|
||||
|
||||
If that variable is not set, applications should try to connect to the well-known address unix:path=/var/run/dbus/system_bus_socket.
|
||||
Implementations of the well-known system bus should listen on an address that will result in that connection being successful.
|
||||
*/
|
||||
SystemBusAddress = "DBUS_SYSTEM_BUS_ADDRESS"
|
||||
|
||||
// FallbackSystemBusAddress is used when [SystemBusAddress] is not set.
|
||||
FallbackSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket"
|
||||
)
|
||||
|
||||
var (
|
||||
address [2]string
|
||||
addressOnce sync.Once
|
||||
)
|
||||
|
||||
// Address returns the session and system bus addresses copied from environment,
|
||||
// or appropriate fallback values if they are not set.
|
||||
func Address() (session, system string) {
|
||||
addressOnce.Do(func() {
|
||||
// resolve upstream session bus address
|
||||
if addr, ok := os.LookupEnv(SessionBusAddress); !ok {
|
||||
// fall back to default format
|
||||
address[0] = fmt.Sprintf("unix:path=/run/user/%d/bus", os.Getuid())
|
||||
} else {
|
||||
address[0] = addr
|
||||
}
|
||||
|
||||
// resolve upstream system bus address
|
||||
if addr, ok := os.LookupEnv(SystemBusAddress); !ok {
|
||||
// fall back to default hardcoded value
|
||||
address[1] = FallbackSystemBusAddress
|
||||
} else {
|
||||
address[1] = addr
|
||||
}
|
||||
})
|
||||
|
||||
return address[0], address[1]
|
||||
}
|
||||
170
internal/dbus/dbus_test.go
Normal file
170
internal/dbus/dbus_test.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package dbus_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/internal/helper"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
func TestFinalise(t *testing.T) {
|
||||
if _, err := dbus.Finalise(dbus.ProxyPair{}, dbus.ProxyPair{}, nil, nil); !errors.Is(err, syscall.EBADE) {
|
||||
t.Errorf("Finalise: error = %v, want %v",
|
||||
err, syscall.EBADE)
|
||||
}
|
||||
|
||||
for id, tc := range testCasePairs {
|
||||
t.Run("create final for "+id, func(t *testing.T) {
|
||||
var wt io.WriterTo
|
||||
if v, err := dbus.Finalise(tc[0].bus, tc[1].bus, tc[0].c, tc[1].c); (errors.Is(err, syscall.EINVAL)) != tc[0].wantErr {
|
||||
t.Errorf("Finalise: error = %v, wantErr %v",
|
||||
err, tc[0].wantErr)
|
||||
return
|
||||
} else {
|
||||
wt = v
|
||||
}
|
||||
|
||||
// rest of the tests happen for sealed instances
|
||||
if tc[0].wantErr {
|
||||
return
|
||||
}
|
||||
|
||||
// build null-terminated string from wanted args
|
||||
want := new(strings.Builder)
|
||||
args := append(tc[0].want, tc[1].want...)
|
||||
for _, arg := range args {
|
||||
want.WriteString(arg)
|
||||
want.WriteByte(0)
|
||||
}
|
||||
|
||||
got := new(strings.Builder)
|
||||
if _, err := wt.WriteTo(got); err != nil {
|
||||
t.Errorf("WriteTo: error = %v", err)
|
||||
}
|
||||
|
||||
if want.String() != got.String() {
|
||||
t.Errorf("Seal: %q, want %q",
|
||||
got.String(), want.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyStartWaitCloseString(t *testing.T) {
|
||||
t.Run("sandbox", func(t *testing.T) { testProxyFinaliseStartWaitCloseString(t, true) })
|
||||
t.Run("direct", func(t *testing.T) { testProxyFinaliseStartWaitCloseString(t, false) })
|
||||
}
|
||||
|
||||
const (
|
||||
stubProxyTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) {
|
||||
{
|
||||
oldWaitDelay := helper.WaitDelay
|
||||
helper.WaitDelay = 16 * time.Second
|
||||
t.Cleanup(func() { helper.WaitDelay = oldWaitDelay })
|
||||
}
|
||||
|
||||
{
|
||||
proxyName := dbus.ProxyName
|
||||
dbus.ProxyName = os.Args[0]
|
||||
t.Cleanup(func() { dbus.ProxyName = proxyName })
|
||||
}
|
||||
|
||||
var p *dbus.Proxy
|
||||
|
||||
t.Run("string for nil proxy", func(t *testing.T) {
|
||||
want := "(invalid dbus proxy)"
|
||||
if got := p.String(); got != want {
|
||||
t.Errorf("String: %q, want %q",
|
||||
got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid start", func(t *testing.T) {
|
||||
if !useSandbox {
|
||||
p = dbus.NewDirect(t.Context(), message.New(nil), nil, nil)
|
||||
} else {
|
||||
p = dbus.New(t.Context(), message.New(nil), nil, nil)
|
||||
}
|
||||
|
||||
if err := p.Start(); !errors.Is(err, syscall.ENOTRECOVERABLE) {
|
||||
t.Errorf("Start: error = %q, wantErr %q", err, syscall.ENOTRECOVERABLE)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
for id, tc := range testCasePairs {
|
||||
// this test does not test errors
|
||||
if tc[0].wantErr {
|
||||
continue
|
||||
}
|
||||
|
||||
t.Run("proxy for "+id, func(t *testing.T) {
|
||||
var final *dbus.Final
|
||||
t.Run("finalise", func(t *testing.T) {
|
||||
if v, err := dbus.Finalise(tc[0].bus, tc[1].bus, tc[0].c, tc[1].c); err != nil {
|
||||
t.Errorf("Finalise: error = %v, wantErr %v", err, tc[0].wantErr)
|
||||
return
|
||||
} else {
|
||||
final = v
|
||||
}
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(t.Context(), stubProxyTimeout)
|
||||
defer cancel()
|
||||
output := new(strings.Builder)
|
||||
if !useSandbox {
|
||||
p = dbus.NewDirect(ctx, message.New(nil), final, output)
|
||||
} else {
|
||||
p = dbus.New(ctx, message.New(nil), final, output)
|
||||
}
|
||||
|
||||
{ // check invalid wait behaviour
|
||||
wantErr := "dbus: not started"
|
||||
if err := p.Wait(); err == nil || err.Error() != wantErr {
|
||||
t.Errorf("Wait: error = %v, wantErr %v", err, wantErr)
|
||||
}
|
||||
}
|
||||
|
||||
{ // check string behaviour
|
||||
want := "(unused dbus proxy)"
|
||||
if got := p.String(); got != want {
|
||||
t.Errorf("String: %q, want %q", got, want)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.Start(); err != nil {
|
||||
t.Fatalf("Start: error = %v", err)
|
||||
}
|
||||
|
||||
{ // check running string behaviour
|
||||
wantSubstr := fmt.Sprintf("%s --args=3 --fd=4", os.Args[0])
|
||||
if useSandbox {
|
||||
wantSubstr = `argv: ["xdg-dbus-proxy" "--args=3" "--fd=4"], filter: true, rules: 0, flags: 0x1, presets: 0xf`
|
||||
}
|
||||
if got := p.String(); !strings.Contains(got, wantSubstr) {
|
||||
t.Errorf("String: %q, want %q",
|
||||
got, wantSubstr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
p.Close()
|
||||
if err := p.Wait(); err != nil {
|
||||
t.Errorf("Wait: error = %v\noutput: %s", err, output.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
15
internal/dbus/export_test.go
Normal file
15
internal/dbus/export_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
// NewDirect returns a new instance of [Proxy] with its sandbox disabled.
|
||||
func NewDirect(ctx context.Context, msg message.Msg, final *Final, output io.Writer) *Proxy {
|
||||
p := New(ctx, msg, final, output)
|
||||
p.useSandbox = false
|
||||
return p
|
||||
}
|
||||
188
internal/dbus/proc.go
Normal file
188
internal/dbus/proc.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/check"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/internal/helper"
|
||||
"hakurei.app/ldd"
|
||||
)
|
||||
|
||||
// Start starts and configures a D-Bus proxy process.
|
||||
func (p *Proxy) Start() error {
|
||||
if p.final == nil || p.final.WriterTo == nil {
|
||||
return syscall.ENOTRECOVERABLE
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.pmu.Lock()
|
||||
defer p.pmu.Unlock()
|
||||
|
||||
if p.cancel != nil || p.cause != nil {
|
||||
return errors.New("dbus: already started")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancelCause(p.ctx)
|
||||
|
||||
if !p.useSandbox {
|
||||
p.helper = helper.NewDirect(ctx, p.name, p.final, true, argF, func(cmd *exec.Cmd) {
|
||||
if p.output != nil {
|
||||
cmd.Stdout, cmd.Stderr = p.output, p.output
|
||||
}
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
cmd.Env = make([]string, 0)
|
||||
}, nil)
|
||||
} else {
|
||||
var toolPath *check.Absolute
|
||||
if a, err := check.NewAbs(p.name); err != nil {
|
||||
if p.name, err = exec.LookPath(p.name); err != nil {
|
||||
return err
|
||||
} else if toolPath, err = check.NewAbs(p.name); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
toolPath = a
|
||||
}
|
||||
|
||||
var libPaths []*check.Absolute
|
||||
if entries, err := ldd.Resolve(ctx, p.msg, toolPath); err != nil {
|
||||
return err
|
||||
} else {
|
||||
libPaths = ldd.Path(entries)
|
||||
}
|
||||
|
||||
p.helper = helper.New(
|
||||
ctx, p.msg, toolPath, "xdg-dbus-proxy",
|
||||
p.final, true,
|
||||
argF, func(z *container.Container) {
|
||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
||||
z.SeccompPresets |= std.PresetStrict
|
||||
z.Hostname = "hakurei-dbus"
|
||||
if p.output != nil {
|
||||
z.Stdout, z.Stderr = p.output, p.output
|
||||
}
|
||||
|
||||
// these lib paths are unpredictable, so mount them first so they cannot cover anything
|
||||
for _, name := range libPaths {
|
||||
z.Bind(name, name, 0)
|
||||
}
|
||||
|
||||
// upstream bus directories
|
||||
upstreamPaths := make([]*check.Absolute, 0, 2)
|
||||
for _, addr := range [][]AddrEntry{p.final.SessionUpstream, p.final.SystemUpstream} {
|
||||
for _, ent := range addr {
|
||||
if ent.Method != "unix" {
|
||||
continue
|
||||
}
|
||||
for _, pair := range ent.Values {
|
||||
if pair[0] != "path" {
|
||||
continue
|
||||
}
|
||||
if a, err := check.NewAbs(pair[1]); err != nil {
|
||||
continue
|
||||
} else {
|
||||
upstreamPaths = append(upstreamPaths, a.Dir())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
check.SortAbs(upstreamPaths)
|
||||
upstreamPaths = check.CompactAbs(upstreamPaths)
|
||||
for _, name := range upstreamPaths {
|
||||
z.Bind(name, name, 0)
|
||||
}
|
||||
z.HostNet = len(upstreamPaths) == 0
|
||||
z.HostAbstract = z.HostNet
|
||||
|
||||
// parent directories of bind paths
|
||||
sockDirPaths := make([]*check.Absolute, 0, 2)
|
||||
if a, err := check.NewAbs(p.final.Session[1]); err == nil {
|
||||
sockDirPaths = append(sockDirPaths, a.Dir())
|
||||
}
|
||||
if a, err := check.NewAbs(p.final.System[1]); err == nil {
|
||||
sockDirPaths = append(sockDirPaths, a.Dir())
|
||||
}
|
||||
check.SortAbs(sockDirPaths)
|
||||
sockDirPaths = check.CompactAbs(sockDirPaths)
|
||||
for _, name := range sockDirPaths {
|
||||
z.Bind(name, name, std.BindWritable)
|
||||
}
|
||||
|
||||
// xdg-dbus-proxy bin path
|
||||
binPath := toolPath.Dir()
|
||||
z.Bind(binPath, binPath, 0)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
if err := p.helper.Start(); err != nil {
|
||||
cancel(err)
|
||||
p.helper = nil
|
||||
return err
|
||||
}
|
||||
|
||||
p.cancel, p.cause = cancel, func() error { return context.Cause(ctx) }
|
||||
return nil
|
||||
}
|
||||
|
||||
var proxyClosed = errors.New("proxy closed")
|
||||
|
||||
// Wait blocks until xdg-dbus-proxy exits and releases resources.
|
||||
func (p *Proxy) Wait() error {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
|
||||
p.pmu.RLock()
|
||||
if p.helper == nil || p.cancel == nil || p.cause == nil {
|
||||
p.pmu.RUnlock()
|
||||
return errors.New("dbus: not started")
|
||||
}
|
||||
|
||||
var errs [3]error
|
||||
|
||||
errs[0] = p.helper.Wait()
|
||||
if errors.Is(errs[0], context.Canceled) &&
|
||||
errors.Is(p.cause(), proxyClosed) {
|
||||
errs[0] = nil
|
||||
}
|
||||
p.pmu.RUnlock()
|
||||
|
||||
// ensure socket removal so ephemeral directory is empty at revert
|
||||
if err := os.Remove(p.final.Session[1]); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
errs[1] = err
|
||||
}
|
||||
if p.final.System[1] != "" {
|
||||
if err := os.Remove(p.final.System[1]); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
errs[2] = err
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs[:]...)
|
||||
}
|
||||
|
||||
// Close cancels the context passed to the helper instance attached to xdg-dbus-proxy.
|
||||
func (p *Proxy) Close() {
|
||||
p.pmu.Lock()
|
||||
defer p.pmu.Unlock()
|
||||
|
||||
if p.cancel == nil {
|
||||
panic("dbus: not started")
|
||||
}
|
||||
p.cancel(proxyClosed)
|
||||
}
|
||||
|
||||
func argF(argsFd, statFd int) []string {
|
||||
if statFd == -1 {
|
||||
return []string{"--args=" + strconv.Itoa(argsFd)}
|
||||
} else {
|
||||
return []string{"--args=" + strconv.Itoa(argsFd), "--fd=" + strconv.Itoa(statFd)}
|
||||
}
|
||||
}
|
||||
11
internal/dbus/proc_test.go
Normal file
11
internal/dbus/proc_test.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package dbus_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal/helper"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) { container.TryArgv0(nil); helper.InternalHelperStub(); os.Exit(m.Run()) }
|
||||
105
internal/dbus/proxy.go
Normal file
105
internal/dbus/proxy.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/helper"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
// ProxyName is the file name or path to the proxy program.
|
||||
// Overriding ProxyName will only affect Proxy instance created after the change.
|
||||
var ProxyName = "xdg-dbus-proxy"
|
||||
|
||||
// Proxy holds the state of a xdg-dbus-proxy process, and should never be copied.
|
||||
type Proxy struct {
|
||||
helper helper.Helper
|
||||
ctx context.Context
|
||||
msg message.Msg
|
||||
|
||||
cancel context.CancelCauseFunc
|
||||
cause func() error
|
||||
|
||||
final *Final
|
||||
output io.Writer
|
||||
useSandbox bool
|
||||
|
||||
name string
|
||||
|
||||
mu, pmu sync.RWMutex
|
||||
}
|
||||
|
||||
func (p *Proxy) String() string {
|
||||
if p == nil {
|
||||
return "(invalid dbus proxy)"
|
||||
}
|
||||
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
|
||||
if p.helper != nil {
|
||||
return p.helper.String()
|
||||
}
|
||||
|
||||
return "(unused dbus proxy)"
|
||||
}
|
||||
|
||||
// Final describes the outcome of a proxy configuration.
|
||||
type Final struct {
|
||||
Session, System ProxyPair
|
||||
// parsed upstream address
|
||||
SessionUpstream, SystemUpstream []AddrEntry
|
||||
io.WriterTo
|
||||
}
|
||||
|
||||
// Finalise creates a checked argument writer for [Proxy].
|
||||
func Finalise(sessionBus, systemBus ProxyPair, session, system *hst.BusConfig) (final *Final, err error) {
|
||||
if session == nil && system == nil {
|
||||
return nil, syscall.EBADE
|
||||
}
|
||||
|
||||
var args []string
|
||||
if session != nil {
|
||||
if err = session.CheckInterfaces("session"); err != nil {
|
||||
return
|
||||
}
|
||||
args = append(args, Args(session, sessionBus)...)
|
||||
}
|
||||
if system != nil {
|
||||
if err = system.CheckInterfaces("system"); err != nil {
|
||||
return
|
||||
}
|
||||
args = append(args, Args(system, systemBus)...)
|
||||
}
|
||||
|
||||
final = &Final{Session: sessionBus, System: systemBus}
|
||||
|
||||
final.WriterTo, err = helper.NewCheckedArgs(args...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if session != nil {
|
||||
final.SessionUpstream, err = Parse([]byte(final.Session[0]))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if system != nil {
|
||||
final.SystemUpstream, err = Parse([]byte(final.System[0]))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// New returns a new instance of [Proxy].
|
||||
func New(ctx context.Context, msg message.Msg, final *Final, output io.Writer) *Proxy {
|
||||
return &Proxy{name: ProxyName, ctx: ctx, msg: msg, final: final, output: output, useSandbox: true}
|
||||
}
|
||||
214
internal/dbus/samples_test.go
Normal file
214
internal/dbus/samples_test.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package dbus_test
|
||||
|
||||
import (
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
const (
|
||||
sampleHostPath = "/tmp/bus"
|
||||
sampleHostAddr = "unix:path=" + sampleHostPath
|
||||
sampleBindPath = "/tmp/proxied_bus"
|
||||
)
|
||||
|
||||
var samples = []dbusTestCase{
|
||||
{
|
||||
"org.chromium.Chromium", &hst.BusConfig{
|
||||
See: nil,
|
||||
Talk: []string{"org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver",
|
||||
"org.freedesktop.secrets", "org.kde.kwalletd5", "org.kde.kwalletd6", "org.gnome.SessionManager"},
|
||||
Own: []string{"org.chromium.Chromium.*", "org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||
"org.mpris.MediaPlayer2.chromium.*"},
|
||||
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
||||
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
||||
Log: false,
|
||||
Filter: true,
|
||||
}, false, false,
|
||||
[2]string{sampleHostAddr, sampleBindPath},
|
||||
[]string{
|
||||
sampleHostAddr,
|
||||
sampleBindPath,
|
||||
"--filter",
|
||||
"--talk=org.freedesktop.Notifications",
|
||||
"--talk=org.freedesktop.FileManager1",
|
||||
"--talk=org.freedesktop.ScreenSaver",
|
||||
"--talk=org.freedesktop.secrets",
|
||||
"--talk=org.kde.kwalletd5",
|
||||
"--talk=org.kde.kwalletd6",
|
||||
"--talk=org.gnome.SessionManager",
|
||||
"--own=org.chromium.Chromium.*",
|
||||
"--own=org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||
"--own=org.mpris.MediaPlayer2.chromium.*",
|
||||
"--call=org.freedesktop.portal.*=*",
|
||||
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
|
||||
},
|
||||
},
|
||||
{
|
||||
"org.chromium.Chromium+", &hst.BusConfig{
|
||||
See: nil,
|
||||
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
|
||||
Own: nil,
|
||||
Call: nil,
|
||||
Broadcast: nil,
|
||||
Log: false,
|
||||
Filter: true,
|
||||
}, false, false,
|
||||
[2]string{sampleHostAddr, sampleBindPath},
|
||||
[]string{
|
||||
sampleHostAddr,
|
||||
sampleBindPath,
|
||||
"--filter",
|
||||
"--talk=org.bluez",
|
||||
"--talk=org.freedesktop.Avahi",
|
||||
"--talk=org.freedesktop.UPower",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
"dev.vencord.Vesktop", &hst.BusConfig{
|
||||
See: nil,
|
||||
Talk: []string{"org.freedesktop.Notifications", "org.kde.StatusNotifierWatcher"},
|
||||
Own: []string{"dev.vencord.Vesktop.*", "org.mpris.MediaPlayer2.dev.vencord.Vesktop.*"},
|
||||
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
||||
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
||||
Log: false,
|
||||
Filter: true,
|
||||
}, false, false,
|
||||
[2]string{sampleHostAddr, sampleBindPath},
|
||||
[]string{
|
||||
sampleHostAddr,
|
||||
sampleBindPath,
|
||||
"--filter",
|
||||
"--talk=org.freedesktop.Notifications",
|
||||
"--talk=org.kde.StatusNotifierWatcher",
|
||||
"--own=dev.vencord.Vesktop.*",
|
||||
"--own=org.mpris.MediaPlayer2.dev.vencord.Vesktop.*",
|
||||
"--call=org.freedesktop.portal.*=*",
|
||||
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*"},
|
||||
},
|
||||
|
||||
{
|
||||
"uk.gensokyo.CrashTestDummy", &hst.BusConfig{
|
||||
See: []string{"uk.gensokyo.CrashTestDummy1"},
|
||||
Talk: []string{"org.freedesktop.Notifications"},
|
||||
Own: []string{"uk.gensokyo.CrashTestDummy.*", "org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy.*"},
|
||||
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
||||
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
||||
Log: true,
|
||||
Filter: true,
|
||||
}, false, false,
|
||||
[2]string{sampleHostAddr, sampleBindPath},
|
||||
[]string{
|
||||
sampleHostAddr,
|
||||
sampleBindPath,
|
||||
"--filter",
|
||||
"--see=uk.gensokyo.CrashTestDummy1",
|
||||
"--talk=org.freedesktop.Notifications",
|
||||
"--own=uk.gensokyo.CrashTestDummy.*",
|
||||
"--own=org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy.*",
|
||||
"--call=org.freedesktop.portal.*=*",
|
||||
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
|
||||
"--log"},
|
||||
},
|
||||
{
|
||||
"uk.gensokyo.CrashTestDummy1", &hst.BusConfig{
|
||||
See: []string{"uk.gensokyo.CrashTestDummy"},
|
||||
Talk: []string{"org.freedesktop.Notifications"},
|
||||
Own: []string{"uk.gensokyo.CrashTestDummy1.*", "org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy1.*"},
|
||||
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
||||
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
||||
Log: true,
|
||||
Filter: true,
|
||||
}, false, true,
|
||||
[2]string{sampleHostAddr, sampleBindPath},
|
||||
[]string{
|
||||
sampleHostAddr,
|
||||
sampleBindPath,
|
||||
"--filter",
|
||||
"--see=uk.gensokyo.CrashTestDummy",
|
||||
"--talk=org.freedesktop.Notifications",
|
||||
"--own=uk.gensokyo.CrashTestDummy1.*",
|
||||
"--own=org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy1.*",
|
||||
"--call=org.freedesktop.portal.*=*",
|
||||
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
|
||||
"--log"},
|
||||
},
|
||||
}
|
||||
|
||||
type dbusTestCase struct {
|
||||
id string
|
||||
c *hst.BusConfig
|
||||
wantErr bool
|
||||
wantErrF bool
|
||||
bus [2]string
|
||||
want []string
|
||||
}
|
||||
|
||||
var (
|
||||
testCasesExt = func() []dbusTestCase {
|
||||
testCases := make([]dbusTestCase, len(samples)*2)
|
||||
for i := range samples {
|
||||
testCases[i] = samples[i]
|
||||
|
||||
fi := &testCases[len(samples)+i]
|
||||
*fi = samples[i]
|
||||
|
||||
// create null-injected test cases
|
||||
fi.wantErr = true
|
||||
injectNulls := func(t *[]string) {
|
||||
f := make([]string, len(*t))
|
||||
for i := range f {
|
||||
f[i] = "\x00" + (*t)[i] + "\x00"
|
||||
}
|
||||
*t = f
|
||||
}
|
||||
|
||||
fi.c = new(hst.BusConfig)
|
||||
*fi.c = *samples[i].c
|
||||
injectNulls(&fi.c.See)
|
||||
injectNulls(&fi.c.Talk)
|
||||
injectNulls(&fi.c.Own)
|
||||
}
|
||||
return testCases
|
||||
}()
|
||||
|
||||
testCasePairs = func() map[string][2]dbusTestCase {
|
||||
// enumerate test case pairs
|
||||
var pc int
|
||||
for _, tc := range samples {
|
||||
if tc.id != "" {
|
||||
pc++
|
||||
}
|
||||
}
|
||||
pairs := make(map[string][2]dbusTestCase, pc)
|
||||
for i, tc := range testCasesExt {
|
||||
if tc.id == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// skip already enumerated system bus test
|
||||
if tc.id[len(tc.id)-1] == '+' {
|
||||
continue
|
||||
}
|
||||
|
||||
ftp := [2]dbusTestCase{tc}
|
||||
|
||||
// system proxy tests always place directly after its user counterpart with id ending in +
|
||||
if i+1 < len(testCasesExt) && testCasesExt[i+1].id[len(testCasesExt[i+1].id)-1] == '+' {
|
||||
// attach system bus config
|
||||
ftp[1] = testCasesExt[i+1]
|
||||
|
||||
// check for misplaced/mismatching tests
|
||||
if ftp[0].wantErr != ftp[1].wantErr || ftp[0].id+"+" != ftp[1].id {
|
||||
panic("mismatching session/system pairing")
|
||||
}
|
||||
}
|
||||
|
||||
k := tc.id
|
||||
if tc.wantErr {
|
||||
k = "malformed_" + k
|
||||
}
|
||||
pairs[k] = ftp
|
||||
}
|
||||
return pairs
|
||||
}()
|
||||
)
|
||||
18
internal/dbus/testdata/dev.vencord.Vesktop.json
vendored
Normal file
18
internal/dbus/testdata/dev.vencord.Vesktop.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"talk":[
|
||||
"org.freedesktop.Notifications",
|
||||
"org.kde.StatusNotifierWatcher"
|
||||
],
|
||||
"own":[
|
||||
"dev.vencord.Vesktop.*",
|
||||
"org.mpris.MediaPlayer2.dev.vencord.Vesktop.*"
|
||||
],
|
||||
"call":{
|
||||
"org.freedesktop.portal.*":"*"
|
||||
},
|
||||
"broadcast":{
|
||||
"org.freedesktop.portal.*":"@/org/freedesktop/portal/*"
|
||||
},
|
||||
|
||||
"filter":true
|
||||
}
|
||||
9
internal/dbus/testdata/org.chromium.Chromium+.json
vendored
Normal file
9
internal/dbus/testdata/org.chromium.Chromium+.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"talk":[
|
||||
"org.bluez",
|
||||
"org.freedesktop.Avahi",
|
||||
"org.freedesktop.UPower"
|
||||
],
|
||||
|
||||
"filter":true
|
||||
}
|
||||
24
internal/dbus/testdata/org.chromium.Chromium.json
vendored
Normal file
24
internal/dbus/testdata/org.chromium.Chromium.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"talk":[
|
||||
"org.freedesktop.Notifications",
|
||||
"org.freedesktop.FileManager1",
|
||||
"org.freedesktop.ScreenSaver",
|
||||
"org.freedesktop.secrets",
|
||||
"org.kde.kwalletd5",
|
||||
"org.kde.kwalletd6",
|
||||
"org.gnome.SessionManager"
|
||||
],
|
||||
"own":[
|
||||
"org.chromium.Chromium.*",
|
||||
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||
"org.mpris.MediaPlayer2.chromium.*"
|
||||
],
|
||||
"call":{
|
||||
"org.freedesktop.portal.*":"*"
|
||||
},
|
||||
"broadcast":{
|
||||
"org.freedesktop.portal.*":"@/org/freedesktop/portal/*"
|
||||
},
|
||||
|
||||
"filter":true
|
||||
}
|
||||
21
internal/dbus/testdata/uk.gensokyo.CrashTestDummy.json
vendored
Normal file
21
internal/dbus/testdata/uk.gensokyo.CrashTestDummy.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"see": [
|
||||
"uk.gensokyo.CrashTestDummy1"
|
||||
],
|
||||
"talk":[
|
||||
"org.freedesktop.Notifications"
|
||||
],
|
||||
"own":[
|
||||
"uk.gensokyo.CrashTestDummy.*",
|
||||
"org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy.*"
|
||||
],
|
||||
"call":{
|
||||
"org.freedesktop.portal.*":"*"
|
||||
},
|
||||
"broadcast":{
|
||||
"org.freedesktop.portal.*":"@/org/freedesktop/portal/*"
|
||||
},
|
||||
|
||||
"log": true,
|
||||
"filter":true
|
||||
}
|
||||
Reference in New Issue
Block a user