IDEA-105472 (correct quoting of Windows shell commands)

This commit is contained in:
Roman Shevchenko
2013-04-22 18:14:49 +02:00
parent 0bae6faace
commit c658a5b09e
7 changed files with 164 additions and 90 deletions
@@ -15,6 +15,7 @@
*/
package com.intellij.execution.configurations;
import com.intellij.execution.CommandLineUtil;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.process.ProcessNotCreatedException;
import com.intellij.ide.IdeBundle;
@@ -22,7 +23,6 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.util.containers.ContainerUtil;
@@ -55,7 +55,6 @@ public class GeneralCommandLine implements UserDataHolder {
Key.create("GeneralCommandLine.do.not.escape.quotes");
private static final Logger LOG = Logger.getInstance("#com.intellij.execution.configurations.GeneralCommandLine");
private static final char QUOTE = '\uEFEF';
private String myExePath = null;
private File myWorkDirectory = null;
@@ -70,12 +69,12 @@ public class GeneralCommandLine implements UserDataHolder {
public GeneralCommandLine() {
}
public GeneralCommandLine(final String... command) {
public GeneralCommandLine(@NotNull String... command) {
this(Arrays.asList(command));
}
public GeneralCommandLine(final List<String> command) {
final int size = command.size();
public GeneralCommandLine(@NotNull List<String> command) {
int size = command.size();
if (size > 0) {
setExePath(command.get(0));
if (size > 1) {
@@ -211,13 +210,14 @@ public class GeneralCommandLine implements UserDataHolder {
/**
* Prepares command (quotes and escapes all arguments) and returns it as a newline-separated list
* (suitable e.g. for passing in a environment variable).
* (suitable e.g. for passing in an environment variable).
*
* @return command as a newline-separated list.
*/
@NotNull
public String getPreparedCommandLine() {
return StringUtil.join(prepareCommands(), "\n");
String exePath = myExePath != null ? myExePath : "";
return StringUtil.join(CommandLineUtil.toCommandLine(exePath, myProgramParams.getList()), "\n");
}
public Process createProcess() throws ExecutionException {
@@ -225,14 +225,15 @@ public class GeneralCommandLine implements UserDataHolder {
LOG.debug("Executing [" + getCommandLineString() + "]");
}
String[] commands;
List<String> commands;
try {
checkWorkingDirectory();
commands = prepareCommands();
if (StringUtil.isEmptyOrSpaces(commands[0])) {
if (StringUtil.isEmptyOrSpaces(myExePath)) {
throw new ExecutionException(IdeBundle.message("run.configuration.error.executable.not.specified"));
}
commands = CommandLineUtil.toCommandLine(myExePath, myProgramParams.getList());
}
catch (ExecutionException e) {
LOG.warn(e);
@@ -241,8 +242,7 @@ public class GeneralCommandLine implements UserDataHolder {
try {
ProcessBuilder builder = new ProcessBuilder(commands);
Map<String, String> environment = builder.environment();
setupEnvironment(environment);
setupEnvironment(builder.environment());
builder.directory(myWorkDirectory);
builder.redirectErrorStream(myRedirectErrorStream);
return builder.start();
@@ -266,34 +266,6 @@ public class GeneralCommandLine implements UserDataHolder {
}
}
private String[] prepareCommands() {
final List<String> parameters = myProgramParams.getList();
final String[] result = new String[parameters.size() + 1];
result[0] = myExePath != null ? prepareCommand(FileUtil.toSystemDependentName(myExePath)) : null;
for (int i = 0; i < parameters.size(); i++) {
result[i + 1] = prepareCommand(parameters.get(i));
}
return result;
}
// please keep in sync with com.intellij.rt.execution.junit.ProcessBuilder.prepareCommand() && org.jetbrains.jps.incremental.ExternalProcessUtil.prepareCommand()
public static String prepareCommand(String parameter) {
if (SystemInfo.isWindows) {
if (parameter.contains("\"")) {
parameter = StringUtil.replace(parameter, "\"", "\\\"");
}
else if (parameter.length() == 0) {
parameter = "\"\"";
}
}
if (parameter.length() >= 2 && parameter.charAt(0) == QUOTE && parameter.charAt(parameter.length() - 1) == QUOTE) {
parameter = '"' + parameter.substring(1, parameter.length() - 1) + '"';
}
return parameter;
}
private void setupEnvironment(final Map<String, String> environment) {
if (!myPassParentEnvironment) {
environment.clear();
@@ -327,7 +299,7 @@ public class GeneralCommandLine implements UserDataHolder {
*/
@NotNull
public static String inescapableQuote(@NotNull String parameter) {
return QUOTE + parameter + QUOTE;
return CommandLineUtil.specialQuote(parameter);
}
@Override
@@ -0,0 +1,80 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.execution;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class CommandLineUtil {
private static final char SPECIAL_QUOTE = '\uEFEF';
private static final String WIN_SHELL_SPECIALS = "&<>()@^|";
public static @NotNull String specialQuote(@NotNull String parameter) {
return quote(parameter, SPECIAL_QUOTE);
}
public static @NotNull List<String> toCommandLine(@NotNull List<String> command) {
assert command.size() > 0;
return toCommandLine(command.get(0), command.subList(1, command.size()));
}
// please keep an implementation in sync with [junit-rt] ProcessBuilder.createProcess()
public static @NotNull List<String> toCommandLine(@NotNull String command, @NotNull List<String> parameters) {
List<String> commandLine = ContainerUtil.newArrayListWithExpectedSize(parameters.size() + 1);
commandLine.add(FileUtil.toSystemDependentName(command));
boolean winShell = SystemInfo.isWindows &&
("cmd".equalsIgnoreCase(command) || "cmd.exe".equalsIgnoreCase(command)) &&
parameters.size() > 1 && "/c".equalsIgnoreCase(parameters.get(0));
for (String parameter : parameters) {
if (SystemInfo.isWindows) {
if (parameter.contains("\"")) {
parameter = StringUtil.replace(parameter, "\"", "\\\"");
}
else if (parameter.length() == 0) {
parameter = "\"\"";
}
}
if (winShell && StringUtil.containsAnyChar(parameter, WIN_SHELL_SPECIALS)) {
parameter = quote(parameter, SPECIAL_QUOTE);
}
if (isQuoted(parameter, SPECIAL_QUOTE)) {
parameter = quote(parameter.substring(1, parameter.length() - 1), '"');
}
commandLine.add(parameter);
}
return commandLine;
}
private static String quote(String s, char ch) {
return !isQuoted(s, ch) ? ch + s + ch : s;
}
private static boolean isQuoted(String s, char ch) {
return s.length() >= 2 && s.charAt(0) == ch && s.charAt(s.length() - 1) == ch;
}
}
@@ -15,6 +15,7 @@
*/
package org.jetbrains.plugins.javaFX.packaging;
import com.intellij.execution.CommandLineUtil;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.io.FileUtil;
@@ -23,7 +24,10 @@ import com.intellij.util.Base64Converter;
import com.intellij.util.PathUtilRt;
import com.intellij.util.io.ZipUtil;
import java.io.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
@@ -97,7 +101,7 @@ public abstract class AbstractJavaFxPackager {
buf.append("<target name=\"build artifact\" xmlns:fx=\"javafx:com.sun.javafx.tools.ant\">");
final String artifactFileName = getArtifactRootName();
final String artifactName = FileUtil.getNameWithoutExtension(artifactFileName);
final List<JavaFxAntGenerator.SimpleTag> tags =
final List<JavaFxAntGenerator.SimpleTag> tags =
JavaFxAntGenerator.createJarAndDeployTasks(this, artifactFileName, artifactName, tempUnzippedArtifactOutput.getPath());
for (JavaFxAntGenerator.SimpleTag tag : tags) {
tag.generate(buf);
@@ -228,16 +232,15 @@ public abstract class AbstractJavaFxPackager {
registerJavaFxPackagerError(ex.getMessage());
}
protected abstract String prepareParam(String param);
private void addParameter(List<String> commandLine, String param) {
private static void addParameter(List<String> commandLine, String param) {
if (!StringUtil.isEmptyOrSpaces(param)) {
commandLine.add(prepareParam(param));
commandLine.add(param);
}
}
private int startProcess(List<String> commands) {
try {
final Process process = new ProcessBuilder(commands).start();
final Process process = new ProcessBuilder(CommandLineUtil.toCommandLine(commands)).start();
final String message = new String(FileUtil.loadBytes(process.getErrorStream()));
if (!StringUtil.isEmptyOrSpaces(message)) {
registerJavaFxPackagerError(message);
@@ -262,9 +265,9 @@ public abstract class AbstractJavaFxPackager {
commands.add("-Dant.home=" + antHome);
commands.add("-classpath");
commands.add(antHome + "/lib/ant.jar" + File.pathSeparator +
commands.add(antHome + "/lib/ant.jar" + File.pathSeparator +
antHome + "/lib/ant-launcher.jar" + File.pathSeparator +
javaHome + "/lib/ant-javafx.jar" + File.pathSeparator +
javaHome + "/lib/ant-javafx.jar" + File.pathSeparator +
javaHome + "/jre/lib/jfxrt.jar");
commands.add("org.apache.tools.ant.launch.Launcher");
commands.add("-f");
@@ -154,7 +154,7 @@ public class JavaFxAntTaskTest extends UsefulTestCase{
if (preloaderJar != null) {
packager.setPreloaderJar(preloaderJar);
}
final List<JavaFxAntGenerator.SimpleTag> temp = JavaFxAntGenerator
.createJarAndDeployTasks(packager, artifactFileName, artifactName, "temp");
final StringBuilder buf = new StringBuilder();
@@ -163,7 +163,7 @@ public class JavaFxAntTaskTest extends UsefulTestCase{
}
assertEquals(expected
.replaceAll("temp/deploy", "temp\\" + File.separator + "deploy")
.replaceAll("temp/" + artifactFileName, "temp\\" + File.separator + artifactFileName),
.replaceAll("temp/" + artifactFileName, "temp\\" + File.separator + artifactFileName),
buf.toString());
}
@@ -275,11 +275,6 @@ public class JavaFxAntTaskTest extends UsefulTestCase{
protected void registerJavaFxPackagerError(String message) {
}
@Override
protected String prepareParam(String param) {
return param;
}
@Override
public String getKeypass() {
return null;
@@ -1,10 +1,24 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.javaFX;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.builders.artifacts.ArtifactBuildTaskProvider;
import org.jetbrains.jps.incremental.BuildTask;
import org.jetbrains.jps.incremental.CompileContext;
import org.jetbrains.jps.incremental.ExternalProcessUtil;
import org.jetbrains.jps.incremental.ProjectBuildException;
import org.jetbrains.jps.incremental.messages.BuildMessage;
import org.jetbrains.jps.incremental.messages.CompilerMessage;
@@ -144,11 +158,6 @@ public class JpsJavaFxArtifactBuildTaskProvider extends ArtifactBuildTaskProvide
myCompileContext.processMessage(new CompilerMessage(COMPILER_NAME, BuildMessage.Kind.ERROR, message));
}
@Override
protected String prepareParam(String param) {
return ExternalProcessUtil.prepareCommand(param);
}
@Override
protected String getHtmlParamFile() {
return myProperties.myState.getHtmlParamFile();
@@ -15,7 +15,6 @@
*/
package org.jetbrains.plugins.javaFX.packaging;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.compiler.CompileContext;
import com.intellij.openapi.compiler.CompilerMessageCategory;
@@ -370,16 +369,11 @@ public class JavaFxArtifactProperties extends ArtifactProperties<JavaFxArtifactP
return myProperties.isConvertCss2Bin();
}
@Override
protected String prepareParam(String param) {
return GeneralCommandLine.prepareCommand(param);
}
@Override
protected String getHtmlParamFile() {
return myProperties.getHtmlParamFile();
}
@Override
protected String getParamFile() {
return myProperties.getParamFile();
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,8 @@ import java.util.List;
public class ProcessBuilder {
public static final boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
private static final String WIN_SHELL_SPECIALS = "&<>()@^|";
private final List myParameters = new ArrayList();
public void add(final String parameter) {
@@ -37,36 +39,55 @@ public class ProcessBuilder {
}
}
// please keep an implementation in sync with [util] CommandLineUtil.toCommandLine()
public Process createProcess() throws IOException {
if (myParameters.size() < 1) {
throw new IllegalArgumentException("Executable name not specified");
}
final String[] command = new String[myParameters.size()];
for (int i = 0; i < myParameters.size(); i++) {
command[i] = prepareCommand(myParameters.get(i).toString());
String command = myParameters.get(0).toString();
boolean winShell = isWindows &&
("cmd".equalsIgnoreCase(command) || "cmd.exe".equalsIgnoreCase(command)) &&
myParameters.size() > 1 && "/c".equalsIgnoreCase(myParameters.get(0).toString());
String[] commandLine = new String[myParameters.size()];
commandLine[0] = command;
for (int i = 1; i < myParameters.size(); i++) {
String parameter = myParameters.get(i).toString();
if (isWindows) {
int pos = parameter.indexOf('\"');
if (pos >= 0) {
StringBuffer buffer = new StringBuffer(parameter);
do {
buffer.insert(pos, '\\');
pos += 2;
}
while ((pos = parameter.indexOf('\"', pos)) >= 0);
parameter = buffer.toString();
}
else if (parameter.length() == 0) {
parameter = "\"\"";
}
if (winShell && containsAnyChar(parameter, WIN_SHELL_SPECIALS)) {
parameter = '"' + parameter + '"';
}
}
commandLine[i] = parameter;
}
return Runtime.getRuntime().exec(command);
return Runtime.getRuntime().exec(commandLine);
}
// please keep in sync with GeneralCommandLine.prepareCommand()
private static String prepareCommand(String parameter) {
if (isWindows) {
int pos = parameter.indexOf('\"');
if (pos >= 0) {
final StringBuffer buffer = new StringBuffer(parameter);
do {
buffer.insert(pos, '\\');
pos += 2;
}
while ((pos = parameter.indexOf('\"', pos)) >= 0);
parameter = buffer.toString();
}
else if (parameter.length() == 0) {
parameter = "\"\"";
private static boolean containsAnyChar(String value, String chars) {
for (int i = 0; i < value.length(); i++) {
if (chars.indexOf(value.charAt(i)) >= 0) {
return true;
}
}
return parameter;
return false;
}
}