-
Notifications
You must be signed in to change notification settings - Fork 229
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #302 from kolyshkin/escape-bus-address
Introduce and use EscapeBusAddressValue
- Loading branch information
Showing
4 changed files
with
144 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |