Fix i3-dmenu-desktop quoting (#5162)
Commit 70f23caa9a
introduced new issues.
Instead of distinguishing " and \, as that commit attempted,
let’s instead keep the level of escaping by escaping each backslash,
just like each double quote.
I tested this with:
# recommended way to quote $ and " in quoted arguments, not ambiguous
Exec=/tmp/logargs "hello \\$PWD \\"and\\" more"
# permitted way to quote $ and " in quoted arguments, but ambiguous
Exec=/tmp/logargs "hello \$PWD \"and\" more"
# permitted way to quote arguments, slightly unusual to quote first arg
Exec="/tmp/logargs" hey
# a complicated shell expression, not ambiguous
Exec=sh -c "if [ -n \\"\\$*\\" ]; then exec /tmp/logargs --alternate-editor= --display=\\"\\$DISPLAY\\" \\"\\$@\\"; else exec /tmp/logargs --alternate-editor= --create-frame; fi" placeholder %F
related to https://github.com/i3/i3/issues/4697 (electrum, original)
related to https://github.com/i3/i3/issues/5152 (phpstorm, breakage)
related to https://github.com/i3/i3/issues/5156 (emacsclient, breakage)
This commit is contained in:
parent
8ec41334ec
commit
812ec43d46
@ -413,7 +413,7 @@ my $exec = $app->{Exec};
|
|||||||
my $location = $app->{_Location};
|
my $location = $app->{_Location};
|
||||||
|
|
||||||
# Quote as described by “The Exec key”:
|
# Quote as described by “The Exec key”:
|
||||||
# https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
|
# https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s07.html
|
||||||
sub quote {
|
sub quote {
|
||||||
my ($str) = @_;
|
my ($str) = @_;
|
||||||
$str =~ s/("|`|\$|\\)/\\$1/g;
|
$str =~ s/("|`|\$|\\)/\\$1/g;
|
||||||
@ -425,6 +425,17 @@ $choice = quote($choice);
|
|||||||
$location = quote($location);
|
$location = quote($location);
|
||||||
$name = quote($name);
|
$name = quote($name);
|
||||||
|
|
||||||
|
# https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s07.html:
|
||||||
|
#
|
||||||
|
# Note that the general escape rule for values of type string states that the
|
||||||
|
# backslash character can be escaped as ("\\") as well and that this escape rule
|
||||||
|
# is applied before the quoting rule. As such, to unambiguously represent a
|
||||||
|
# literal backslash character in a quoted argument in a desktop entry file
|
||||||
|
# requires the use of four successive backslash characters ("\\\\"). Likewise, a
|
||||||
|
# literal dollar sign in a quoted argument in a desktop entry file is
|
||||||
|
# unambiguously represented with ("\\$").
|
||||||
|
$exec =~ s/\\\\/\\/g;
|
||||||
|
|
||||||
# Remove deprecated field codes, as the spec dictates.
|
# Remove deprecated field codes, as the spec dictates.
|
||||||
$exec =~ s/%[dDnNvm]//g;
|
$exec =~ s/%[dDnNvm]//g;
|
||||||
|
|
||||||
@ -481,9 +492,10 @@ EOT
|
|||||||
# starts with a double quote ("), everything is parsed as-is until the next
|
# starts with a double quote ("), everything is parsed as-is until the next
|
||||||
# double quote which is NOT preceded by a backslash (\).
|
# double quote which is NOT preceded by a backslash (\).
|
||||||
#
|
#
|
||||||
# Therefore, we escape all double quotes (") by replacing them with \"
|
# Therefore, we escape all double quotes (") by replacing them with \".
|
||||||
$exec =~ s/\\"/\\\\\\"/g;
|
# To not change the meaning of any double quote, backslashes need to be
|
||||||
$exec =~ s/([^\\])"/$1\\"/g;
|
# escaped as well.
|
||||||
|
$exec =~ s/(["\\])/\\$1/g;
|
||||||
|
|
||||||
if (exists($app->{StartupNotify}) && !$app->{StartupNotify}) {
|
if (exists($app->{StartupNotify}) && !$app->{StartupNotify}) {
|
||||||
$nosn = '--no-startup-id';
|
$nosn = '--no-startup-id';
|
||||||
|
125
testcases/t/318-i3-dmenu-desktop.t
Normal file
125
testcases/t/318-i3-dmenu-desktop.t
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
#!perl
|
||||||
|
# vim:ts=4:sw=4:expandtab
|
||||||
|
#
|
||||||
|
# Please read the following documents before working on tests:
|
||||||
|
# • https://build.i3wm.org/docs/testsuite.html
|
||||||
|
# (or docs/testsuite)
|
||||||
|
#
|
||||||
|
# • https://build.i3wm.org/docs/lib-i3test.html
|
||||||
|
# (alternatively: perldoc ./testcases/lib/i3test.pm)
|
||||||
|
#
|
||||||
|
# • https://build.i3wm.org/docs/ipc.html
|
||||||
|
# (or docs/ipc)
|
||||||
|
#
|
||||||
|
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||||
|
# (unless you are already familiar with Perl)
|
||||||
|
#
|
||||||
|
# Verifies that i3-dmenu-desktop correctly parses Exec= lines in .desktop files
|
||||||
|
# and sends the command to i3 for execution.
|
||||||
|
# Ticket: #5152, #5156
|
||||||
|
# Bug still in: 4.21-17-g389d555d
|
||||||
|
use i3test;
|
||||||
|
use i3test::Util qw(slurp);
|
||||||
|
use File::Temp qw(tempfile tempdir);
|
||||||
|
use POSIX qw(mkfifo);
|
||||||
|
use JSON::XS qw(decode_json);
|
||||||
|
|
||||||
|
my $desktopdir = tempdir(CLEANUP => 1);
|
||||||
|
|
||||||
|
$ENV{XDG_DATA_DIRS} = "$desktopdir";
|
||||||
|
|
||||||
|
mkdir("$desktopdir/applications");
|
||||||
|
|
||||||
|
# Create an i3-msg executable that dumps command line flags to a FIFO
|
||||||
|
my $tmpdir = tempdir(CLEANUP => 1);
|
||||||
|
|
||||||
|
$ENV{PATH} = "$tmpdir:" . $ENV{PATH};
|
||||||
|
|
||||||
|
mkfifo("$tmpdir/fifo", 0600) or BAIL_OUT "Could not create FIFO: $!";
|
||||||
|
|
||||||
|
open(my $i3msg_dump, '>', "$tmpdir/i3-msg");
|
||||||
|
say $i3msg_dump <<EOT;
|
||||||
|
#!/usr/bin/env perl
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use JSON::XS qw(encode_json);
|
||||||
|
open(my \$f, '>', "$tmpdir/fifo");
|
||||||
|
say \$f encode_json(\\\@ARGV);
|
||||||
|
close(\$f);
|
||||||
|
EOT
|
||||||
|
close($i3msg_dump);
|
||||||
|
chmod 0755, "$tmpdir/i3-msg";
|
||||||
|
|
||||||
|
my $testcnt = 0;
|
||||||
|
sub verify_exec {
|
||||||
|
my ($execline, $want_arg) = @_;
|
||||||
|
|
||||||
|
$testcnt++;
|
||||||
|
|
||||||
|
open(my $desktop, '>', "$desktopdir/applications/desktop$testcnt.desktop");
|
||||||
|
say $desktop <<EOT;
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=i3-testsuite-$testcnt
|
||||||
|
Type=Application
|
||||||
|
Exec=$execline
|
||||||
|
EOT
|
||||||
|
close($desktop);
|
||||||
|
|
||||||
|
# complete-run.pl arranges for $PATH to be set up such that the
|
||||||
|
# i3-dmenu-desktop version we execute is the one from the build directory.
|
||||||
|
my $exit = system("i3-dmenu-desktop --dmenu 'echo i3-testsuite-$testcnt' &");
|
||||||
|
if ($exit != 0) {
|
||||||
|
die "failed to run i3-dmenu-desktop";
|
||||||
|
}
|
||||||
|
|
||||||
|
chomp($want_arg); # trim trailing newline
|
||||||
|
my $got_args = decode_json(slurp("$tmpdir/fifo"));
|
||||||
|
is_deeply($got_args, [ $want_arg ], 'i3-dmenu-desktop executed command as expected');
|
||||||
|
}
|
||||||
|
|
||||||
|
# recommended number of backslashes by the spec, not ambiguous
|
||||||
|
my $exec_1 = <<'EOS';
|
||||||
|
echo "hello \\$PWD \\"and\\" more"
|
||||||
|
EOS
|
||||||
|
my $want_1 = <<'EOS';
|
||||||
|
exec "echo \"hello \\$PWD \\\"and\\\" more\""
|
||||||
|
EOS
|
||||||
|
verify_exec($exec_1, $want_1);
|
||||||
|
|
||||||
|
# permitted, but ambiguous
|
||||||
|
my $exec_2 = <<'EOS';
|
||||||
|
echo "hello \$PWD \"and\" more"
|
||||||
|
EOS
|
||||||
|
my $want_2 = <<'EOS';
|
||||||
|
exec "echo \"hello \\$PWD \\\"and\\\" more\""
|
||||||
|
EOS
|
||||||
|
verify_exec($exec_2, $want_2);
|
||||||
|
|
||||||
|
# electrum
|
||||||
|
my $exec_3 = <<'EOS';
|
||||||
|
sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\"; electrum %u"
|
||||||
|
EOS
|
||||||
|
my $want_3 = <<'EOS';
|
||||||
|
exec "sh -c \"PATH=\\\"\\$HOME/.local/bin:\\$PATH\\\"; electrum \""
|
||||||
|
EOS
|
||||||
|
verify_exec($exec_3, $want_3);
|
||||||
|
|
||||||
|
# complicated emacsclient command
|
||||||
|
my $exec_4 = <<'EOS';
|
||||||
|
sh -c "if [ -n \\"\\$*\\" ]; then exec emacsclient --alternate-editor= --display=\\"\\$DISPLAY\\" \\"\\$@\\"; else exec emacsclient --alternate-editor= --create-frame; fi" placeholder %F
|
||||||
|
EOS
|
||||||
|
my $want_4 = <<'EOS';
|
||||||
|
exec "sh -c \"if [ -n \\\"\\$*\\\" ]; then exec emacsclient --alternate-editor= --display=\\\"\\$DISPLAY\\\" \\\"\\$@\\\"; else exec emacsclient --alternate-editor= --create-frame; fi\" placeholder "
|
||||||
|
EOS
|
||||||
|
verify_exec($exec_4, $want_4);
|
||||||
|
|
||||||
|
# permitted, but unusual to quote the first arg
|
||||||
|
my $exec_5 = <<'EOS';
|
||||||
|
"electrum" arg
|
||||||
|
EOS
|
||||||
|
my $want_5 = <<'EOS';
|
||||||
|
exec "\"electrum\" arg"
|
||||||
|
EOS
|
||||||
|
verify_exec($exec_5, $want_5);
|
||||||
|
|
||||||
|
done_testing;
|
Loading…
Reference in New Issue
Block a user