Skip to content

Commit

Permalink
Introduce and use EscapeBusAddressValue
Browse files Browse the repository at this point in the history
D-Bus specification [1] requires that the values in server address need
to be escaped in a special way, and other clients perform the needed
escaping (e.g. systemd [2] does that).

More to say, if dbus sees a character that should have been escaped, it
returns an error [3].

Add EscapeBusAddressValue function (with some tests and a benchmark),
and use it from tryDiscoverDbusSessionBusAddress().

The function is exported as it might be of use to other packages (in
particular, runc [4]).

NOTE that the function does not escape '*' symbol, because de-facto it
is the list of optionally-escaped characters since 2007, but that is not
yet documented in D-Bus spec (see [5] for more details).

[1] https://dbus.freedesktop.org/doc/dbus-specification.html#addresses
[2] https://github.com/systemd/systemd/blob/5efbd0bf897a990ebe43d7dc69141d87c404ac9a/src/libsystemd/sd-bus/bus-internal.c#L294-L318
[3] https://gitlab.freedesktop.org/dbus/dbus/-/blob/37b76d13738e782fe2eb12abdd0179745c0b3f81/dbus/dbus-address.c#L330
[4] opencontainers/runc#3356
[5] https://gitlab.freedesktop.org/dbus/dbus/-/merge_requests/248

Signed-off-by: Kir Kolyshkin <[email protected]>
  • Loading branch information
kolyshkin committed Feb 2, 2022
1 parent 6b744b9 commit 47b6dc1
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 1 deletion.
2 changes: 1 addition & 1 deletion conn_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func tryDiscoverDbusSessionBusAddress() string {
if runUserBusFile := path.Join(runtimeDirectory, "bus"); fileExists(runUserBusFile) {
// if /run/user/<uid>/bus exists, that file itself
// *is* the unix socket, so return its path
return fmt.Sprintf("unix:path=%s", runUserBusFile)
return fmt.Sprintf("unix:path=%s", EscapeBusAddressValue(runUserBusFile))
}
if runUserSessionDbusFile := path.Join(runtimeDirectory, "dbus-session"); fileExists(runUserSessionDbusFile) {
// if /run/user/<uid>/dbus-session exists, it's a
Expand Down
68 changes: 68 additions & 0 deletions escape.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package dbus

import "strings"

// EscapeBusAddressValue implements a requirement to escape the values
// in D-Bus server addresses, as defined by the D-Bus specification at
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses.
func EscapeBusAddressValue(val string) string {
toEsc := strNeedsEscape(val)
if toEsc == 0 {
// Avoid unneeded allocation/copying.
return val
}

var out strings.Builder
// Every to-be-escaped byte needs 3 bytes, i.e. 2 extra bytes.
out.Grow(len(val) + 2*toEsc)

for i := 0; i < len(val); i++ {
ch := val[i]
if !needsEscape(ch) {
_ = out.WriteByte(ch)

continue
}
// Convert ch to %xx, where xx is hex value.
_ = out.WriteByte('%')
_ = out.WriteByte(hexchar(ch >> 4))
_ = out.WriteByte(hexchar(ch & 0x0F))
}

return out.String()
}

// hexchar returns an octal representation of a n, where n < 16.
// For invalid values of n, the function panics.
func hexchar(n byte) byte {
const hex = "0123456789abcdef"

// For n >= len(hex), runtime will panic.
return hex[n]
}

// needsEscape tells if a byte is NOT one of optionally-escaped bytes.
func needsEscape(c byte) bool {
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {
return false
}
switch c {
case '-', '_', '/', '\\', '.', '*':
return false
}

return true
}

// strNeedsEscape tells how many bytes in the string need escaping.
func strNeedsEscape(val string) int {
count := 0

for i := 0; i < len(val); i++ {
if needsEscape(val[i]) {
count++
}
}

return count
}
47 changes: 47 additions & 0 deletions escape_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package dbus

import (
"net/url"
"testing"
)

var escapeTestCases = []struct {
in, out string
}{
{in: "", out: ""},
{in: "ABCDabcdZYXzyx01289", out: "ABCDabcdZYXzyx01289"},
{in: `_-/\*`, out: `_-/\*`},
{in: `=+:~!`, out: `%3d%2b%3a%7e%21`},
{in: `space here`, out: `space%20here`},
{in: `Привет`, out: `%d0%9f%d1%80%d0%b8%d0%b2%d0%b5%d1%82`},
{in: `ჰეი`, out: `%e1%83%b0%e1%83%94%e1%83%98`},
{in: `你好`, out: `%e4%bd%a0%e5%a5%bd`},
{in: `こんにちは`, out: `%e3%81%93%e3%82%93%e3%81%ab%e3%81%a1%e3%81%af`},
}

func TestEscapeBusAddressValue(t *testing.T) {
for _, tc := range escapeTestCases {
out := EscapeBusAddressValue(tc.in)
if out != tc.out {
t.Errorf("input: %q; want %q, got %q", tc.in, tc.out, out)
}
// Use QueryUnescape to unescape %xx back to bytes.
in, err := url.QueryUnescape(out)
if err != nil {
t.Errorf("unescape error: %v", err)
} else if in != tc.in {
t.Errorf("unescape: want %q, got %q", tc.in, in)
}
}
}

func BenchmarkEscapeBusAddressValue(b *testing.B) {
var out string

for i := 0; i < b.N; i++ {
for _, tc := range escapeTestCases {
out = EscapeBusAddressValue(tc.in)
}
}
b.Log("out:", out)
}

0 comments on commit 47b6dc1

Please sign in to comment.