Skip to content

Commit

Permalink
Merge pull request #302 from kolyshkin/escape-bus-address
Browse files Browse the repository at this point in the history
Introduce and use EscapeBusAddressValue
  • Loading branch information
guelfey authored Feb 13, 2022
2 parents 3500ad4 + 48d30c5 commit 041560b
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 2 deletions.
7 changes: 6 additions & 1 deletion conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,12 @@ func getKey(s, key string) string {
for _, keyEqualsValue := range strings.Split(s, ",") {
keyValue := strings.SplitN(keyEqualsValue, "=", 2)
if len(keyValue) == 2 && keyValue[0] == key {
return keyValue[1]
val, err := UnescapeBusAddressValue(keyValue[1])
if err != nil {
// No way to return an error.
return ""
}
return val
}
}
return ""
Expand Down
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
84 changes: 84 additions & 0 deletions escape.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package dbus

import "net/url"

// 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
}

// Avoid allocation for short paths.
var buf [64]byte
var out []byte
// Every to-be-escaped byte needs 2 extra bytes.
required := len(val) + 2*toEsc
if required <= len(buf) {
out = buf[:required]
} else {
out = make([]byte, required)
}

j := 0
for i := 0; i < len(val); i++ {
if ch := val[i]; needsEscape(ch) {
// Convert ch to %xx, where xx is hex value.
out[j] = '%'
out[j+1] = hexchar(ch >> 4)
out[j+2] = hexchar(ch & 0x0F)
j += 3
} else {
out[j] = ch
j++
}
}

return string(out)
}

// UnescapeBusAddressValue unescapes values in D-Bus server addresses,
// as defined by the D-Bus specification at
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses.
func UnescapeBusAddressValue(val string) (string, error) {
// Looks like url.PathUnescape does exactly what is required.
return url.PathUnescape(val)
}

// 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
}
53 changes: 53 additions & 0 deletions escape_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package dbus

import (
"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`},
}

// More real world examples for more fair benchmark.
var escapeBenchmarkCases = []struct {
in, out string
}{
{in: "/run/user/1000/bus", out: "/run/user/1000/bus"},
{in: "/path/with/a/single space/bus", out: "/path/with/a/single%20space/bus"},
}

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)
}
in, err := UnescapeBusAddressValue(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 escapeBenchmarkCases {
out = EscapeBusAddressValue(tc.in)
}
}
b.Log("out:", out)
}

0 comments on commit 041560b

Please sign in to comment.