client/systray: add installer for a freedesktop autostart file (#18767)
Adds freedesktop as an option for installing autostart desktop files for starting the systray application. Fixes #18766 Signed-off-by: Claus Lensbøl <claus@tailscale.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package freedesktop provides helpers for freedesktop systems.
|
||||
package freedesktop
|
||||
|
||||
import "strings"
|
||||
|
||||
const needsEscape = " \t\n\"'\\><~|&;$*?#()`"
|
||||
|
||||
var escaper = strings.NewReplacer(`"`, `\"`, "`", "\\`", `$`, `\$`, `\`, `\\`)
|
||||
|
||||
// Quote quotes according to the Desktop Entry Specification, as below:
|
||||
//
|
||||
// Arguments may be quoted in whole. If an argument contains a reserved
|
||||
// character the argument must be quoted. The rules for quoting of arguments is
|
||||
// also applicable to the executable name or path of the executable program as
|
||||
// provided.
|
||||
//
|
||||
// Quoting must be done by enclosing the argument between double quotes and
|
||||
// escaping the double quote character, backtick character ("`"), dollar sign
|
||||
// ("$") and backslash character ("\") by preceding it with an additional
|
||||
// backslash character. Implementations must undo quoting before expanding field
|
||||
// codes and before passing the argument to the executable program. Reserved
|
||||
// characters are space (" "), tab, newline, double quote, single quote ("'"),
|
||||
// backslash character ("\"), greater-than sign (">"), less-than sign ("<"),
|
||||
// tilde ("~"), vertical bar ("|"), ampersand ("&"), semicolon (";"), dollar
|
||||
// sign ("$"), asterisk ("*"), question mark ("?"), hash mark ("#"), parenthesis
|
||||
// ("(") and (")") and backtick character ("`").
|
||||
func Quote(s string) string {
|
||||
if s == "" {
|
||||
return `""`
|
||||
}
|
||||
if !strings.ContainsAny(s, needsEscape) {
|
||||
return s
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(`"`)
|
||||
escaper.WriteString(&b, s)
|
||||
b.WriteString(`"`)
|
||||
return b.String()
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package freedesktop
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEscape(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, input, want string
|
||||
}{
|
||||
{
|
||||
name: "no illegal chars",
|
||||
input: "/home/user",
|
||||
want: "/home/user",
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
want: "\"\"",
|
||||
},
|
||||
{
|
||||
name: "space",
|
||||
input: " ",
|
||||
want: "\" \"",
|
||||
},
|
||||
{
|
||||
name: "tab",
|
||||
input: "\t",
|
||||
want: "\"\t\"",
|
||||
},
|
||||
{
|
||||
name: "newline",
|
||||
input: "\n",
|
||||
want: "\"\n\"",
|
||||
},
|
||||
{
|
||||
name: "double quote",
|
||||
input: "\"",
|
||||
want: "\"\\\"\"",
|
||||
},
|
||||
{
|
||||
name: "single quote",
|
||||
input: "'",
|
||||
want: "\"'\"",
|
||||
},
|
||||
{
|
||||
name: "backslash",
|
||||
input: "\\",
|
||||
want: "\"\\\\\"",
|
||||
},
|
||||
{
|
||||
name: "greater than",
|
||||
input: ">",
|
||||
want: "\">\"",
|
||||
},
|
||||
{
|
||||
name: "less than",
|
||||
input: "<",
|
||||
want: "\"<\"",
|
||||
},
|
||||
{
|
||||
name: "tilde",
|
||||
input: "~",
|
||||
want: "\"~\"",
|
||||
},
|
||||
{
|
||||
name: "pipe",
|
||||
input: "|",
|
||||
want: "\"|\"",
|
||||
},
|
||||
{
|
||||
name: "ampersand",
|
||||
input: "&",
|
||||
want: "\"&\"",
|
||||
},
|
||||
{
|
||||
name: "semicolon",
|
||||
input: ";",
|
||||
want: "\";\"",
|
||||
},
|
||||
{
|
||||
name: "dollar",
|
||||
input: "$",
|
||||
want: "\"\\$\"",
|
||||
},
|
||||
{
|
||||
name: "asterisk",
|
||||
input: "*",
|
||||
want: "\"*\"",
|
||||
},
|
||||
{
|
||||
name: "question mark",
|
||||
input: "?",
|
||||
want: "\"?\"",
|
||||
},
|
||||
{
|
||||
name: "hash",
|
||||
input: "#",
|
||||
want: "\"#\"",
|
||||
},
|
||||
{
|
||||
name: "open paren",
|
||||
input: "(",
|
||||
want: "\"(\"",
|
||||
},
|
||||
{
|
||||
name: "close paren",
|
||||
input: ")",
|
||||
want: "\")\"",
|
||||
},
|
||||
{
|
||||
name: "backtick",
|
||||
input: "`",
|
||||
want: "\"\\`\"",
|
||||
},
|
||||
{
|
||||
name: "char without escape",
|
||||
input: "/home/user\t",
|
||||
want: "\"/home/user\t\"",
|
||||
},
|
||||
{
|
||||
name: "char with escape",
|
||||
input: "/home/user\\",
|
||||
want: "\"/home/user\\\\\"",
|
||||
},
|
||||
{
|
||||
name: "all illegal chars",
|
||||
input: "/home/user" + needsEscape,
|
||||
want: "\"/home/user \t\n\\\"'\\\\><~|&;\\$*?#()\\`\"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Quote(tt.input)
|
||||
if strings.Compare(got, tt.want) != 0 {
|
||||
t.Errorf("expected %s, got %s", tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user