Fix quoting of custom launch commands

This commit is contained in:
crschnick committed 2026-10-07 23:51:47 +00:00
1 parent 3d29778561
commit 31bf55d97d
6 files changed
+37 -28

No files matched your search

@@ -6,11 +6,13 @@ import io.xpipe.app.process.CommandBuilder;
import io.xpipe.app.process.CommandSupport;
import io.xpipe.app.process.LocalShell;
import io.xpipe.app.util.FilePath;
import io.xpipe.app.util.OsType;
import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
import java.util.stream.Collectors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ExternalApplicationHelper {
@@ -30,27 +32,43 @@ public class ExternalApplicationHelper {
}
raw = raw.strip();
var split = Arrays.asList(raw.split("\\s+"));
if (split.size() == 0) {
if (raw.isEmpty()) {
return;
}
var split = Arrays.asList(raw.split("\\s+"));
String exec;
String args;
if (raw.startsWith("\"")) {
var end = raw.substring(1).indexOf("\"");
// The executable can be quoted with either double or single quotes
if (raw.startsWith("\"") || raw.startsWith("'")) {
var quote = raw.charAt(0);
var end = raw.indexOf(quote, 1);
if (end == -1) {
return;
}
end++;
exec = raw.substring(1, end);
args = raw.substring(end + 1).strip();
} else {
exec = split.getFirst();
args = split.stream().skip(1).collect(Collectors.joining(" "));
// Keep the original spacing, as it might be part of quoted arguments
args = raw.substring(exec.length()).strip();
}
startAsync(CommandBuilder.of().addFile(exec).add(args));
// On Windows, the arguments are passed as-is to the program, which only understands double quotes
if (OsType.ofLocal() == OsType.WINDOWS) {
args = convertSingleQuotedArguments(args);
}
startAsync(CommandBuilder.of().addFile(exec).addIf(!args.isEmpty(), args));
}
private static final Pattern SINGLE_QUOTED_ARGUMENT = Pattern.compile("'((?:[^']|'')*)'");
private static String convertSingleQuotedArguments(String args) {
return SINGLE_QUOTED_ARGUMENT
.matcher(args)
.replaceAll(m -> Matcher.quoteReplacement("\"" + m.group(1).replace("''", "'") + "\""));
}
public static void startAsync(CommandBuilder b) throws Exception {
@@ -502,12 +502,11 @@ public interface ExternalEditorType extends PrefsChoiceValue {
var format =
customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE";
var command = CommandBuilder.of()
.add(ExternalApplicationHelper.replaceVariableArgument(format, "FILE", file.toString(), true));
var command = ExternalApplicationHelper.replaceVariableArgument(format, "FILE", file.toString(), true);
if (AppPrefs.get().customEditorCommandInTerminal().get()) {
TerminalLaunch.builder()
.title(file.toString())
.localScript(sc -> new ShellScript(command.buildFull(sc)))
.localScript(sc -> new ShellScript(command))
.logIfEnabled(false)
.preferTabs(false)
.pauseOnExit(false)
@@ -45,13 +45,12 @@ public class CustomRdpClient implements ExternalApplicationType, ExternalRdpClie
}
var format = command.toLowerCase(Locale.ROOT).contains("$file") ? command : command + " $FILE";
ExternalApplicationHelper.startAsync(CommandBuilder.of()
.add(ExternalApplicationHelper.replaceVariableArgument(
ExternalApplicationHelper.startAsync(ExternalApplicationHelper.replaceVariableArgument(
format,
"FILE",
writeRdpConfigFile(configuration.getTitle(), configuration.getConfig())
.toString(),
true)));
true));
}
@Override
@@ -45,7 +45,7 @@ public class CustomSpiceClient implements ExternalSpiceClient {
var format = command.toLowerCase(Locale.ROOT).contains("$file") ? command : command + " $FILE";
var toExecute = ExternalApplicationHelper.replaceVariableArgument(
format, "FILE", configuration.getFile().toString(), true);
ExternalApplicationHelper.startAsync(CommandBuilder.of().add(toExecute));
ExternalApplicationHelper.startAsync(toExecute);
}
@Override
@@ -35,18 +35,11 @@ public class CustomTerminalType implements ExternalApplicationType, ExternalTerm
}
var format = custom.toLowerCase(Locale.ROOT).contains("$cmd") ? custom : custom + " $CMD";
try (var sc = LocalShell.getShell()) {
var toExecute = ExternalApplicationHelper.replaceVariableArgument(
format, "CMD", configuration.single().getScriptFile().toString(), true);
// We can't be sure whether the command is blocking or not, so always make it not blocking
if (sc.getOsType() == OsType.WINDOWS) {
toExecute = CommandBuilder.of().add("start").addLiteral(configuration.getCleanTitle()).add(toExecute).buildFull(sc);
} else {
var async = sc.getShellDialect().launchAsync(CommandBuilder.of().add(toExecute), true);
toExecute = async.buildFull(sc);
}
sc.command(toExecute).execute();
}
ExternalApplicationHelper.startAsync(ExternalApplicationHelper.replaceVariableArgument(
format,
"CMD",
configuration.single().getScriptFile().toString(),
true));
}
@Override
@@ -45,7 +45,7 @@ public class CustomVncClient implements ExternalVncClient {
var address = configuration.getHost() + ":" + configuration.getPort();
var format = command.toLowerCase(Locale.ROOT).contains("$address") ? command : command + " $ADDRESS";
var toExecute = ExternalApplicationHelper.replaceVariableArgument(format, "ADDRESS", address, true);
ExternalApplicationHelper.startAsync(CommandBuilder.of().add(toExecute));
ExternalApplicationHelper.startAsync(toExecute);
}
@Override