# HG changeset patch # User uta # Date 1361540955 -14400 # Node ID d12c06e6e2baca49db2abe086e8ab30e446cfe88 # Parent 17f574546dc890a5560a45f789f691e97de2be45 8005942: (process) Improved Runtime.exec Reviewed-by: alanb, ahgross diff -r 17f574546dc8 -r d12c06e6e2ba jdk/src/share/classes/java/lang/ProcessBuilder.java --- a/jdk/src/share/classes/java/lang/ProcessBuilder.java Fri Feb 15 13:49:38 2013 +0400 +++ b/jdk/src/share/classes/java/lang/ProcessBuilder.java Fri Feb 22 17:49:15 2013 +0400 @@ -30,6 +30,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.FileOutputStream; +import java.security.AccessControlException; import java.util.Arrays; import java.util.ArrayList; import java.util.List; @@ -1024,13 +1025,24 @@ redirects, redirectErrorStream); } catch (IOException e) { + String exceptionInfo = ": " + e.getMessage(); + Throwable cause = e; + if (security != null) { + // Can not disclose the fail reason for read-protected files. + try { + security.checkRead(prog); + } catch (AccessControlException ace) { + exceptionInfo = ""; + cause = ace; + } + } // It's much easier for us to create a high-quality error // message than the low-level C code which found the problem. throw new IOException( "Cannot run program \"" + prog + "\"" + (dir == null ? "" : " (in directory \"" + dir + "\")") - + ": " + e.getMessage(), - e); + + exceptionInfo, + cause); } } } diff -r 17f574546dc8 -r d12c06e6e2ba jdk/src/windows/classes/java/lang/ProcessImpl.java --- a/jdk/src/windows/classes/java/lang/ProcessImpl.java Fri Feb 15 13:49:38 2013 +0400 +++ b/jdk/src/windows/classes/java/lang/ProcessImpl.java Fri Feb 22 17:49:15 2013 +0400 @@ -145,6 +145,88 @@ } + // We guarantee the only command file execution for implicit [cmd.exe] run. + // http://technet.microsoft.com/en-us/library/bb490954.aspx + private static final char CMD_BAT_ESCAPE[] = {' ', '\t', '<', '>', '&', '|', '^'}; + private static final char WIN32_EXECUTABLE_ESCAPE[] = {' ', '\t', '<', '>'}; + + private static boolean isQuoted(boolean noQuotesInside, String arg, + String errorMessage) { + int lastPos = arg.length() - 1; + if (lastPos >=1 && arg.charAt(0) == '"' && arg.charAt(lastPos) == '"') { + // The argument has already been quoted. + if (noQuotesInside) { + if (arg.indexOf('"', 1) != lastPos) { + // There is ["] inside. + throw new IllegalArgumentException(errorMessage); + } + } + return true; + } + if (noQuotesInside) { + if (arg.indexOf('"') >= 0) { + // There is ["] inside. + throw new IllegalArgumentException(errorMessage); + } + } + return false; + } + + private static boolean needsEscaping(boolean isCmdFile, String arg) { + // Switch off MS heuristic for internal ["]. + // Please, use the explicit [cmd.exe] call + // if you need the internal ["]. + // Example: "cmd.exe", "/C", "Extended_MS_Syntax" + + // For [.exe] or [.com] file the unpaired/internal ["] + // in the argument is not a problem. + boolean argIsQuoted = isQuoted(isCmdFile, arg, + "Argument has embedded quote, use the explicit CMD.EXE call."); + + if (!argIsQuoted) { + char testEscape[] = isCmdFile + ? CMD_BAT_ESCAPE + : WIN32_EXECUTABLE_ESCAPE; + for (int i = 0; i < testEscape.length; ++i) { + if (arg.indexOf(testEscape[i]) >= 0) { + return true; + } + } + } + return false; + } + + private static String getExecutablePath(String path) + throws IOException + { + boolean pathIsQuoted = isQuoted(true, path, + "Executable name has embedded quote, split the arguments"); + + // Win32 CreateProcess requires path to be normalized + File fileToRun = new File(pathIsQuoted + ? path.substring(1, path.length() - 1) + : path); + + // From the [CreateProcess] function documentation: + // + // "If the file name does not contain an extension, .exe is appended. + // Therefore, if the file name extension is .com, this parameter + // must include the .com extension. If the file name ends in + // a period (.) with no extension, or if the file name contains a path, + // .exe is not appended." + // + // "If the file name !does not contain a directory path!, + // the system searches for the executable file in the following + // sequence:..." + // + // In practice ANY non-existent path is extended by [.exe] extension + // in the [CreateProcess] funcion with the only exception: + // the path ends by (.) + + return fileToRun.getPath(); + } + + private long handle = 0; private OutputStream stdin_stream; private InputStream stdout_stream; @@ -157,30 +239,31 @@ final boolean redirectErrorStream) throws IOException { - // Win32 CreateProcess requires cmd[0] to be normalized - cmd[0] = new File(cmd[0]).getPath(); + // The [executablePath] is not quoted for any case. + String executablePath = getExecutablePath(cmd[0]); + + // We need to extend the argument verification procedure + // to guarantee the only command file execution for implicit [cmd.exe] + // run. + String upPath = executablePath.toUpperCase(); + boolean isCmdFile = (upPath.endsWith(".CMD") || upPath.endsWith(".BAT")); StringBuilder cmdbuf = new StringBuilder(80); - for (int i = 0; i < cmd.length; i++) { - if (i > 0) { - cmdbuf.append(' '); - } + + // Quotation protects from interpretation of the [path] argument as + // start of longer path with spaces. Quotation has no influence to + // [.exe] extension heuristic. + cmdbuf.append('"'); + cmdbuf.append(executablePath); + cmdbuf.append('"'); + + for (int i = 1; i < cmd.length; i++) { + cmdbuf.append(' '); String s = cmd[i]; - if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0) { - if (s.charAt(0) != '"') { - cmdbuf.append('"'); - cmdbuf.append(s); - if (s.endsWith("\\")) { - cmdbuf.append("\\"); - } - cmdbuf.append('"'); - } else if (s.endsWith("\"")) { - /* The argument has already been quoted. */ - cmdbuf.append(s); - } else { - /* Unmatched quote for the argument. */ - throw new IllegalArgumentException(); - } + if (needsEscaping(isCmdFile, s)) { + cmdbuf.append('"'); + cmdbuf.append(s); + cmdbuf.append('"'); } else { cmdbuf.append(s); }