From 704d9d8817663e8d219a809c38e7851ec9270aba Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Tue, 12 Sep 2023 14:42:03 +0800 Subject: [PATCH] usersession/userd: add OpenDesktopEntry2 D-Bus method https://github.com/snapcore/snapd/pull/13135 --- cmd/snap/cmd_desktop_launch.go | 26 ++++-- cmd/snap/cmd_desktop_launch_test.go | 24 +++++- interfaces/builtin/desktop_launch.go | 2 +- interfaces/builtin/desktop_launch_test.go | 2 +- .../main/interfaces-desktop-launch/task.yaml | 27 ++++++- .../test-app/bin/app.sh | 2 + .../test-app/meta/gui/test-app.desktop | 5 ++ .../test-launcher/bin/dbus-v1.sh | 6 ++ .../test-launcher/bin/dbus-v2.sh | 24 ++++++ .../test-launcher/bin/dbus.sh | 6 -- .../test-launcher/bin/exec.sh | 2 +- .../test-launcher/meta/snap.yaml | 7 +- usersession/userd/export_test.go | 2 + .../userd/privileged_desktop_launcher.go | 81 +++++++++++++++++-- .../userd/privileged_desktop_launcher_test.go | 72 ++++++++++++++++- 15 files changed, 256 insertions(+), 32 deletions(-) create mode 100755 tests/main/interfaces-desktop-launch/test-launcher/bin/dbus-v1.sh create mode 100755 tests/main/interfaces-desktop-launch/test-launcher/bin/dbus-v2.sh delete mode 100755 tests/main/interfaces-desktop-launch/test-launcher/bin/dbus.sh diff --git a/cmd/snap/cmd_desktop_launch.go b/cmd/snap/cmd_desktop_launch.go index 660abce1e17..2433d2d2ced 100644 --- a/cmd/snap/cmd_desktop_launch.go +++ b/cmd/snap/cmd_desktop_launch.go @@ -78,6 +78,19 @@ func cmdlineArgsToUris(args []string) ([]string, error) { return uris, nil } +func collectLaunchEnv() map[string]string { + env := map[string]string{} + for _, key := range []string{ + "DESKTOP_STARTUP_ID", + "XDG_ACTIVATION_TOKEN", + } { + if val := os.Getenv(key); val != "" { + env[key] = val + } + } + return env +} + func (x *cmdDesktopLaunch) Execute([]string) error { if filepath.Clean(x.DesktopFile) != x.DesktopFile { return fmt.Errorf("desktop file has unclean path: %q", x.DesktopFile) @@ -86,11 +99,17 @@ func (x *cmdDesktopLaunch) Execute([]string) error { return fmt.Errorf("only launching snap applications from %s is supported", dirs.SnapDesktopFilesDir) } + uris, err := cmdlineArgsToUris(x.Positional.FilesOrUris) + if err != nil { + return err + } + // If running a desktop file from a confined snap process, // then run via the privileged launcher. if os.Getenv("SNAP") != "" { // Only the application file name is required for launching. desktopFile := filepath.Base(x.DesktopFile) + env := collectLaunchEnv() // Attempt to launch the desktop file via the // privileged launcher, this will check that this snap @@ -100,7 +119,7 @@ func (x *cmdDesktopLaunch) Execute([]string) error { return fmt.Errorf(i18n.G("unable to access privileged desktop launcher: unable to get session bus: %v"), err) } o := conn.Object("io.snapcraft.Launcher", "/io/snapcraft/PrivilegedDesktopLauncher") - call := o.Call("io.snapcraft.PrivilegedDesktopLauncher.OpenDesktopEntry", 0, desktopFile) + call := o.Call("io.snapcraft.PrivilegedDesktopLauncher.OpenDesktopEntry2", 0, desktopFile, x.Action, uris, env) if call.Err != nil { return fmt.Errorf(i18n.G("failed to launch %s via the privileged desktop launcher: %v"), desktopFile, call.Err) } @@ -112,11 +131,6 @@ func (x *cmdDesktopLaunch) Execute([]string) error { return err } - uris, err := cmdlineArgsToUris(x.Positional.FilesOrUris) - if err != nil { - return err - } - var args []string if x.Action == "" { args, err = de.ExpandSnapExec(uris) diff --git a/cmd/snap/cmd_desktop_launch_test.go b/cmd/snap/cmd_desktop_launch_test.go index c18be72899c..03440f9d31e 100644 --- a/cmd/snap/cmd_desktop_launch_test.go +++ b/cmd/snap/cmd_desktop_launch_test.go @@ -79,6 +79,16 @@ func (s *DesktopLaunchSuite) SetUpTest(c *C) { os.Setenv("BAMF_DESKTOP_FILE_HINT", bamfDesktopFileHint) }) os.Unsetenv("BAMF_DESKTOP_FILE_HINT") + desktopStartupID := os.Getenv("DESKTOP_STARTUP_ID") + s.AddCleanup(func() { + os.Setenv("DESKTOP_STARTUP_ID", desktopStartupID) + }) + os.Unsetenv("DESKTOP_STARTUP_ID") + xdgActivationToken := os.Getenv("XDG_ACTIVATION_TOKEN") + s.AddCleanup(func() { + os.Setenv("XDG_ACTIVATION_TOKEN", xdgActivationToken) + }) + os.Unsetenv("XDG_ACTIVATION_TOKEN") } func (s *DesktopLaunchSuite) TestLaunch(c *C) { @@ -167,12 +177,18 @@ func (s *DesktopLaunchSuite) TestDBusLaunch(c *C) { dbus.FieldDestination: dbus.MakeVariant("io.snapcraft.Launcher"), dbus.FieldPath: dbus.MakeVariant(dbus.ObjectPath("/io/snapcraft/PrivilegedDesktopLauncher")), dbus.FieldInterface: dbus.MakeVariant("io.snapcraft.PrivilegedDesktopLauncher"), - dbus.FieldMember: dbus.MakeVariant("OpenDesktopEntry"), - dbus.FieldSignature: dbus.MakeVariant(dbus.ParseSignatureMust("s")), + dbus.FieldMember: dbus.MakeVariant("OpenDesktopEntry2"), + dbus.FieldSignature: dbus.MakeVariant(dbus.ParseSignatureMust("ssasa{ss}")), }) - c.Assert(msg.Body, HasLen, 1) + c.Assert(msg.Body, HasLen, 4) c.Check(msg.Body[0], Equals, "foo_foo.desktop") + c.Check(msg.Body[1], Equals, "action1") + c.Check(msg.Body[2], DeepEquals, []string{"file:///test.txt"}) + c.Check(msg.Body[3], DeepEquals, map[string]string{ + "DESKTOP_STARTUP_ID": "x11-startup-id", + "XDG_ACTIVATION_TOKEN": "wayland-startup-id", + }) reply := &dbus.Message{ Type: dbus.TypeMethodReply, @@ -194,6 +210,8 @@ func (s *DesktopLaunchSuite) TestDBusLaunch(c *C) { defer restore() os.Setenv("SNAP", "launcher-snap") + os.Setenv("DESKTOP_STARTUP_ID", "x11-startup-id") + os.Setenv("XDG_ACTIVATION_TOKEN", "wayland-startup-id") _, err = snap.Parser(snap.Client()).ParseArgs([]string{"routine", "desktop-launch", "--desktop", s.desktopFile, "--action", "action1", "--", "/test.txt"}) c.Check(err, IsNil) diff --git a/interfaces/builtin/desktop_launch.go b/interfaces/builtin/desktop_launch.go index b9bc2b7ad92..463eda95327 100644 --- a/interfaces/builtin/desktop_launch.go +++ b/interfaces/builtin/desktop_launch.go @@ -56,7 +56,7 @@ dbus (send) bus=session path=/io/snapcraft/PrivilegedDesktopLauncher interface=io.snapcraft.PrivilegedDesktopLauncher - member=OpenDesktopEntry + member={OpenDesktopEntry,OpenDesktopEntry2} peer=(label=unconfined), ` diff --git a/interfaces/builtin/desktop_launch_test.go b/interfaces/builtin/desktop_launch_test.go index 7c2f621aa4c..57f9caff176 100644 --- a/interfaces/builtin/desktop_launch_test.go +++ b/interfaces/builtin/desktop_launch_test.go @@ -80,7 +80,7 @@ func (s *desktopLaunchSuite) TestConnectedPlugSnippet(c *C) { c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `Can identify and launch other snaps.`) - c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `member=OpenDesktopEntry`) + c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `member={OpenDesktopEntry,OpenDesktopEntry2}`) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label=unconfined),`) } diff --git a/tests/main/interfaces-desktop-launch/task.yaml b/tests/main/interfaces-desktop-launch/task.yaml index 672f73c4140..3f044e05893 100644 --- a/tests/main/interfaces-desktop-launch/task.yaml +++ b/tests/main/interfaces-desktop-launch/task.yaml @@ -42,7 +42,7 @@ execute: | snap connections test-launcher | MATCH "desktop-launch +test-launcher:desktop-launch +:desktop-launch +manual" echo "The launcher snap can launch other snaps via userd" - tests.session -u test exec test-launcher.dbus \ + tests.session -u test exec test-launcher.dbus-v1 \ test-app_test-app.desktop echo "The app snap records that it has been launched" @@ -52,15 +52,36 @@ execute: | echo "The app was invoked with the arguments in the desktop file" MATCH "^args=arg-before arg-after$" < "$launch_data" + echo "The v2 API supports launching files and startup notification" + rm "$launch_data" + tests.session -u test exec \ + env DESKTOP_STARTUP_ID=x11-startup XDG_ACTIVATION_TOKEN=wayland-startup \ + test-launcher.dbus-v2 test-app_test-app.desktop \ + file:///test1.txt file:///test2.txt + retry -n 5 --wait 1 test -s "$launch_data" + MATCH "^args=arg-before /test1.txt /test2.txt arg-after$" < "$launch_data" + MATCH "^DESKTOP_STARTUP_ID=x11-startup$" < "$launch_data" + MATCH "^XDG_ACTIVATION_TOKEN=wayland-startup$" < "$launch_data" + + echo "The v2 API supports launching actions" + rm "$launch_data" + tests.session -u test exec test-launcher.dbus-v2 \ + -a foo-action test-app_test-app.desktop + retry -n 5 --wait 1 test -s "$launch_data" + MATCH "^args=action$" < "$launch_data" + if ! os.query is-core; then exit 0 fi echo "The launcher snap can also invoke the snap via the desktop file Exec line" rm "$launch_data" - tests.session -u test exec test-launcher.exec \ - test-app_test-app.desktop + tests.session -u test exec \ + env DESKTOP_STARTUP_ID=x11-startup XDG_ACTIVATION_TOKEN=wayland-startup \ + test-launcher.exec test-app_test-app.desktop retry -n 5 --wait 1 test -s "$launch_data" MATCH "^args=arg-before arg-after$" < "$launch_data" + MATCH "^DESKTOP_STARTUP_ID=x11-startup$" < "$launch_data" + MATCH "^XDG_ACTIVATION_TOKEN=wayland-startup$" < "$launch_data" echo "The desktop-launch helper reports errors from the D-Bus service" not tests.session -u test exec test-launcher.cmd \ diff --git a/tests/main/interfaces-desktop-launch/test-app/bin/app.sh b/tests/main/interfaces-desktop-launch/test-app/bin/app.sh index 4849d6854b3..38c2059b122 100755 --- a/tests/main/interfaces-desktop-launch/test-app/bin/app.sh +++ b/tests/main/interfaces-desktop-launch/test-app/bin/app.sh @@ -9,4 +9,6 @@ WAYLAND_DISPLAY=${WAYLAND_DISPLAY} XDG_CURRENT_DESKTOP=${XDG_CURRENT_DESKTOP} XDG_SESSION_DESKTOP=${XDG_SESSION_DESKTOP} XDG_SESSION_TYPE=${XDG_SESSION_TYPE} +DESKTOP_STARTUP_ID=${DESKTOP_STARTUP_ID} +XDG_ACTIVATION_TOKEN=${XDG_ACTIVATION_TOKEN} EOF diff --git a/tests/main/interfaces-desktop-launch/test-app/meta/gui/test-app.desktop b/tests/main/interfaces-desktop-launch/test-app/meta/gui/test-app.desktop index 0c8fa63730b..7e45c667d85 100644 --- a/tests/main/interfaces-desktop-launch/test-app/meta/gui/test-app.desktop +++ b/tests/main/interfaces-desktop-launch/test-app/meta/gui/test-app.desktop @@ -3,3 +3,8 @@ Type=Application Name=test-app Comment=A desktop file for test-app Exec=test-app arg-before %U arg-after +Actions=foo-action + +[Desktop Action foo-action] +Name=test action +Exec=test-app action diff --git a/tests/main/interfaces-desktop-launch/test-launcher/bin/dbus-v1.sh b/tests/main/interfaces-desktop-launch/test-launcher/bin/dbus-v1.sh new file mode 100755 index 00000000000..8727cc2b77a --- /dev/null +++ b/tests/main/interfaces-desktop-launch/test-launcher/bin/dbus-v1.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +exec busctl --user call \ + io.snapcraft.Launcher /io/snapcraft/PrivilegedDesktopLauncher \ + io.snapcraft.PrivilegedDesktopLauncher OpenDesktopEntry "s" \ + "$1" diff --git a/tests/main/interfaces-desktop-launch/test-launcher/bin/dbus-v2.sh b/tests/main/interfaces-desktop-launch/test-launcher/bin/dbus-v2.sh new file mode 100755 index 00000000000..c3609099c87 --- /dev/null +++ b/tests/main/interfaces-desktop-launch/test-launcher/bin/dbus-v2.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +action="" +while getopts "a:" arg; do + case "$arg" in + a) + action="$OPTARG" + ;; + *) + echo "Unexpected option $arg" >&2 + exit 1 + esac +done +shift $((OPTIND-1)) + +desktop="$1" +shift + +exec busctl --user call \ + io.snapcraft.Launcher /io/snapcraft/PrivilegedDesktopLauncher \ + io.snapcraft.PrivilegedDesktopLauncher OpenDesktopEntry2 "ssasa{ss}" \ + "$desktop" "$action" $# "$@" \ + 2 DESKTOP_STARTUP_ID "$DESKTOP_STARTUP_ID" \ + XDG_ACTIVATION_TOKEN "$XDG_ACTIVATION_TOKEN" diff --git a/tests/main/interfaces-desktop-launch/test-launcher/bin/dbus.sh b/tests/main/interfaces-desktop-launch/test-launcher/bin/dbus.sh deleted file mode 100755 index 0e9af61e9a5..00000000000 --- a/tests/main/interfaces-desktop-launch/test-launcher/bin/dbus.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -exec dbus-send --session --print-reply \ - --dest=io.snapcraft.Launcher /io/snapcraft/PrivilegedDesktopLauncher \ - io.snapcraft.PrivilegedDesktopLauncher.OpenDesktopEntry \ - string:"$1" diff --git a/tests/main/interfaces-desktop-launch/test-launcher/bin/exec.sh b/tests/main/interfaces-desktop-launch/test-launcher/bin/exec.sh index 526f01310b1..24e44fa2452 100755 --- a/tests/main/interfaces-desktop-launch/test-launcher/bin/exec.sh +++ b/tests/main/interfaces-desktop-launch/test-launcher/bin/exec.sh @@ -4,7 +4,7 @@ set -e # Extract command line from desktop file desktop_file="/var/lib/snapd/desktop/applications/$1" -cmdline="$(sed -n 's/^Exec=\(.*\)$/\1/p' "$desktop_file")" +cmdline="$(sed -n '0,/^Exec=/ s/^Exec=\(.*\)$/\1/p' "$desktop_file")" # filter out the file argument cmdline="$(echo "$cmdline" | sed 's/%[uUfF]//g')" diff --git a/tests/main/interfaces-desktop-launch/test-launcher/meta/snap.yaml b/tests/main/interfaces-desktop-launch/test-launcher/meta/snap.yaml index 3e4d4eb85e5..56d2c76a3a1 100644 --- a/tests/main/interfaces-desktop-launch/test-launcher/meta/snap.yaml +++ b/tests/main/interfaces-desktop-launch/test-launcher/meta/snap.yaml @@ -8,8 +8,11 @@ apps: cmd: command: bin/cmd.sh plugs: [desktop-launch] - dbus: - command: bin/dbus.sh + dbus-v1: + command: bin/dbus-v1.sh + plugs: [desktop-launch] + dbus-v2: + command: bin/dbus-v2.sh plugs: [desktop-launch] exec: command: bin/exec.sh diff --git a/usersession/userd/export_test.go b/usersession/userd/export_test.go index 12e56bde295..17326c708c3 100644 --- a/usersession/userd/export_test.go +++ b/usersession/userd/export_test.go @@ -32,8 +32,10 @@ func MockSnapFromSender(f func(*dbus.Conn, dbus.Sender) (string, error)) func() } var ( + AppendEnvironment = appendEnvironment DesktopFileSearchPath = desktopFileSearchPath DesktopFileIDToFilename = desktopFileIDToFilename + ValidateURIs = validateURIs VerifyDesktopFileLocation = verifyDesktopFileLocation ) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index f45d62164d5..0f26d2d4008 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -21,6 +21,7 @@ package userd import ( "fmt" + "net/url" "os" "os/exec" "path/filepath" @@ -47,6 +48,12 @@ const privilegedLauncherIntrospectionXML = ` + + + + + + ` // PrivilegedDesktopLauncher implements the 'io.snapcraft.PrivilegedDesktopLauncher' DBus interface. @@ -74,6 +81,10 @@ func (s *PrivilegedDesktopLauncher) IntrospectionData() string { // DBus interface. The desktopFileID is described here: // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sender dbus.Sender) *dbus.Error { + return s.OpenDesktopEntry2(desktopFileID, "", nil, nil, sender) +} + +func (s *PrivilegedDesktopLauncher) OpenDesktopEntry2(desktopFileID string, action string, uris []string, environment map[string]string, sender dbus.Sender) *dbus.Error { desktopFile, err := desktopFileIDToFilename(desktopFileID) if err != nil { return dbus.MakeFailedError(err) @@ -84,31 +95,43 @@ func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sende return dbus.MakeFailedError(err) } + if err := validateURIs(uris); err != nil { + return dbus.MakeFailedError(err) + } + de, err := desktopentry.Read(desktopFile) if err != nil { return dbus.MakeFailedError(err) } - args, err := de.ExpandExec(nil) + var execArgs []string + if action == "" { + execArgs, err = de.ExpandExec(uris) + } else { + execArgs, err = de.ExpandActionExec(action, uris) + } if err != nil { return dbus.MakeFailedError(err) } - err = systemd.EnsureAtLeast(236) - if err == nil { + args := []string{"--user"} + if err = systemd.EnsureAtLeast(236); err == nil { // systemd 236 introduced the --collect option to systemd-run, // which specifies that the unit should be garbage collected // even if it fails. // https://github.com/systemd/systemd/pull/7314 - args = append([]string{"systemd-run", "--user", "--collect", "--"}, args...) - } else if systemd.IsSystemdTooOld(err) { - args = append([]string{"systemd-run", "--user", "--"}, args...) - } else { + args = append(args, "--collect") + } else if !systemd.IsSystemdTooOld(err) { // systemd not available return dbus.MakeFailedError(err) } + if args, err = appendEnvironment(args, environment); err != nil { + return dbus.MakeFailedError(err) + } + args = append(args, "--") + args = append(args, execArgs...) - cmd := exec.Command(args[0], args[1:]...) + cmd := exec.Command("systemd-run", args...) if err := cmd.Run(); err != nil { return dbus.MakeFailedError(fmt.Errorf("cannot run %q: %v", args, err)) @@ -117,6 +140,48 @@ func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sende return nil } +// validateURIs ensures that all of the uris passed are absolute URIs, +// and if they are file URIs that their path component is absolute. +func validateURIs(uris []string) error { + for _, arg := range uris { + if arg == "" { + return fmt.Errorf("passed an empty parameter") + } + uri, err := url.Parse(arg) + if err != nil { + return fmt.Errorf("one of the parameters is not an URI: %s", arg) + } + if !uri.IsAbs() { + return fmt.Errorf("passed a non-absolute URI: %s", arg) + } + if uri.Scheme == "file" { + if uri.Host != "" { + return fmt.Errorf("passed a file URI with a non-empty host: %s", arg) + } + if !filepath.IsAbs(uri.Path) { + return fmt.Errorf("passed a file URI with a relative path: %s", arg) + } + } + } + return nil +} + +// appendEnvironment extends a systemd-run command line to set allowed +// environment variables. +func appendEnvironment(args []string, environment map[string]string) ([]string, error) { + for key, value := range environment { + switch key { + case "DESKTOP_STARTUP_ID", "XDG_ACTIVATION_TOKEN": + // Allow startup notification related + // environment variables + args = append(args, fmt.Sprintf("--setenv=%s=%s", key, value)) + default: + return nil, fmt.Errorf("unknown variables in environment") + } + } + return args, nil +} + var regularFileExists = osutil.RegularFileExists // desktopFileSearchPath returns the list of directories where desktop diff --git a/usersession/userd/privileged_desktop_launcher_test.go b/usersession/userd/privileged_desktop_launcher_test.go index e5f5bab7194..57876308766 100644 --- a/usersession/userd/privileged_desktop_launcher_test.go +++ b/usersession/userd/privileged_desktop_launcher_test.go @@ -23,6 +23,7 @@ import ( "io/ioutil" "os" "path/filepath" + "sort" "strings" . "gopkg.in/check.v1" @@ -53,7 +54,7 @@ func (s *privilegedDesktopLauncherSuite) SetUpTest(c *C) { var rawMircadeDesktop = `[Desktop Entry] X-SnapInstanceName=mircade Name=mircade - Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/mircade_mircade.desktop /snap/bin/mircade + Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/mircade_mircade.desktop /snap/bin/mircade %f Icon=/snap/mircade/143/meta/gui/mircade.png Comment=Sample confined desktop Type=Application @@ -135,3 +136,72 @@ func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntryFailsForNonSnap(c * err := s.launcher.OpenDesktopEntry("shadow-test.desktop", ":some-dbus-sender") c.Check(err, ErrorMatches, `only launching snap applications from .* is supported`) } + +func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntry2SucceedsWithURIs(c *C) { + cmd := testutil.MockCommand(c, "systemd-run", "true") + defer cmd.Restore() + + err := s.launcher.OpenDesktopEntry2("mircade_mircade.desktop", "", []string{"file:///test.txt"}, nil, ":some-dbus-sender") + c.Check(err, IsNil) +} + +func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntry2WithEnv(c *C) { + cmd := testutil.MockCommand(c, "systemd-run", "true") + defer cmd.Restore() + + err := s.launcher.OpenDesktopEntry2("mircade_mircade.desktop", "", []string{"file:///test.txt"}, map[string]string{"XDG_ACTIVATION_TOKEN": "wayland-id"}, ":some-dbus-sender") + c.Check(err, IsNil) +} + +func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntry2FailsWithUnexpectedURI(c *C) { + cmd := testutil.MockCommand(c, "systemd-run", "true") + defer cmd.Restore() + + err := s.launcher.OpenDesktopEntry2("mircade_mircade.desktop", "", []string{"http://example.org"}, nil, ":some-dbus-sender") + c.Check(err, ErrorMatches, `"http://example.org" is not a file URI`) +} + +func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntry2FailsWithEnvironmentVars(c *C) { + cmd := testutil.MockCommand(c, "systemd-run", "true") + defer cmd.Restore() + + err := s.launcher.OpenDesktopEntry2("mircade_mircade.desktop", "", nil, map[string]string{"foo": "bar"}, ":some-dbus-sender") + c.Check(err, ErrorMatches, `unknown variables in environment`) +} + +func (s *privilegedDesktopLauncherSuite) TestAppendEnvironment(c *C) { + // If no environment variables are passed, args is passed + // through unchanged. + args, err := userd.AppendEnvironment([]string{"foo"}, nil) + c.Check(err, IsNil) + c.Check(args, DeepEquals, []string{"foo"}) + + // Startup notification environment variables are passed through + args, err = userd.AppendEnvironment([]string{"foo"}, map[string]string{ + "DESKTOP_STARTUP_ID": "x11-id", + "XDG_ACTIVATION_TOKEN": "wayland-id", + }) + c.Check(err, IsNil) + // Sort the extra arguments in the slice, to remove dependence + // on map iteration order. + sort.Strings(args[1:]) + c.Check(args, DeepEquals, []string{"foo", "--setenv=DESKTOP_STARTUP_ID=x11-id", "--setenv=XDG_ACTIVATION_TOKEN=wayland-id"}) + + // Error out on unexpected variables + args, err = userd.AppendEnvironment([]string{"foo"}, map[string]string{ + "WAYLAND_DISPLAY": "wayland-0", + }) + c.Check(args, IsNil) + c.Check(err, ErrorMatches, `unknown variables in environment`) +} + +func (s *privilegedDesktopLauncherSuite) TestValidateURIs(c *C) { + err := userd.ValidateURIs([]string{"http://param1.com", "file:///param2.txt", "mailto:user@example.org"}) + c.Check(err, IsNil) + err = userd.ValidateURIs([]string{"-param2"}) + c.Check(err, ErrorMatches, `passed a non-absolute URI: -param2`) + err = userd.ValidateURIs([]string{"file://a/test.txt"}) + c.Check(err, ErrorMatches, `passed a file URI with a non-empty host: file://a/test.txt`) + err = userd.ValidateURIs([]string{"file:test.txt"}) + c.Check(err, ErrorMatches, `passed a file URI with a relative path: file:test.txt`) +}