Path: blob/master/test/jdk/java/lang/ProcessBuilder/ArgCheck.java
66644 views
/*1* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/**24* @test25* @bug 828200826* @requires (os.family == "windows")27* @run main/othervm ArgCheck28* @summary Check invocation of exe and non-exe programs using ProcessBuilder29* and arguments with spaces, backslashes, and simple quoting.30*/3132import java.io.BufferedWriter;33import java.io.File;34import java.io.FileWriter;35import java.io.IOException;36import java.io.InputStream;37import java.nio.charset.Charset;38import java.nio.charset.StandardCharsets;39import java.nio.file.Files;40import java.nio.file.Path;41import java.nio.file.Paths;42import java.util.Arrays;43import java.util.ArrayList;44import java.util.List;45import java.util.Objects;4647/**48* Class to check invocation of java, .cmd, and vbs scripts with arguments and various quote cases.49* Can be run standalone to compare results with other Java versions.50*/51public class ArgCheck {5253private static final Path SRC_DIR = Paths.get(System.getProperty("test.src", "."));54private static final Path WORK_DIR = Paths.get(System.getProperty("user.dir", "."));55private static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", "."));5657private static final String ECHO_CMD_PATH = WORK_DIR.resolve("EchoArguments.cmd").toString();58private static final String ECHO_VBS_PATH = WORK_DIR.resolve("EchoArguments.vbs").toString();5960// Test argument containing both a space and a trailing backslash61// Depending on the mode the final backslash may act as an escape that may turn an added quote to a literal quote62private static final String SPACE_AND_BACKSLASH = "SPACE AND BACKSLASH\\";63private static final char DOUBLE_QUOTE = '"';64private static final char BACKSLASH = '\\';6566private static final String AMBIGUOUS_PROP_NAME = "jdk.lang.Process.allowAmbiguousCommands";67private static final String AMBIGUOUS_PROP_VALUE = System.getProperty(AMBIGUOUS_PROP_NAME);68private static final Boolean AMBIGUOUS_PROP_BOOLEAN = AMBIGUOUS_PROP_VALUE == null ? null :69Boolean.valueOf(!"false".equals(AMBIGUOUS_PROP_VALUE));7071private static final List<String> ECHO_JAVA_ARGS = Arrays.asList("java", "-classpath", TEST_CLASSES.toString(), "ArgCheck");72private static final List<String> ECHO_CMD_ARGS = Arrays.asList(ECHO_CMD_PATH);73private static final List<String> ECHO_VBS_ARGS = Arrays.asList("CScript", "/b", ECHO_VBS_PATH);7475/**76* If zero arguments are supplied, run the test cases, by launching each as a child process.77* If there are arguments, then this is a child Java process that prints each argument to stdout.78* The test can be run manually with -Djdk.lang.Process.allowAmbiguousCommands={"true", "false", ""}79* to run a matching subset of the tests.80*/81public static void main(String[] args) throws IOException {82if (args.length > 0) {83// Echo supplied arguments and exit84for (String arg : args)85System.out.println(arg);86return;87}8889System.out.println("Java Version: " + System.getProperty("java.version"));9091createFiles();9293int errors = 0;94int success = 0;95int skipped = 0;9697for (CMD cmd : CASES) {98// If System property jdk.lang.process.allowAmbiguousCommands matches the case, test it99// If undefined, test them all100if (AMBIGUOUS_PROP_BOOLEAN == null ||101AMBIGUOUS_PROP_BOOLEAN.booleanValue() == cmd.allowAmbiguous) {102try {103testCommand(cmd);104success++;105} catch (Exception ex) {106ex.printStackTrace();107errors++;108}109} else {110// skip unmatched cases111skipped++;112}113}114if (skipped > 0) {115System.out.printf("%d cases skipped, they did not match the tests with jdk.lang.Process.allowAmbiguousCommands: %s%n",116skipped, AMBIGUOUS_PROP_BOOLEAN);117}118System.out.printf("\nSuccess: %d, errors: %d%n", success, errors);119if (errors > 0) {120throw new RuntimeException("Errors: " + errors);121}122}123124/**125* A CMD holds the parameters and the expected result of invoking a process with the parameters.126*/127static class CMD {128/**129* Construct a test case.130* @param allowAmbiguous true/false to set property jdk.lang.Process.allowAmbiguousCommands131* @param command list of command parameters to invoke the executable or script132* @param arguments list of arguments (appended to the command)133* @param expected expected lines of output from invoked command134*/135CMD(boolean allowAmbiguous, List<String> command, List<String> arguments, List<String> expected) {136this.allowAmbiguous = allowAmbiguous;137this.command = command;138this.arguments = arguments;139this.expected = expected;140}141142final boolean allowAmbiguous;143final List<String> command;144final List<String> arguments;145final List<String> expected;146}147148/**149* List of cases with the command, arguments, allowAmbiguous setting, and the expected results150*/151static final List<CMD> CASES = Arrays.asList(152153// allowAmbiguousCommands = false, without application supplied double-quotes.154// The space in the argument requires it to be quoted, the final backslash155// must not be allowed to turn the quote that is added into a literal156// instead of closing the quote.157new CMD(false,158ECHO_JAVA_ARGS,159Arrays.asList(SPACE_AND_BACKSLASH, "ARG_1"),160Arrays.asList(SPACE_AND_BACKSLASH, "ARG_1")),161new CMD(false,162ECHO_CMD_ARGS,163Arrays.asList(SPACE_AND_BACKSLASH, "ARG_2"),164Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_2")),165new CMD(false,166ECHO_VBS_ARGS,167Arrays.asList(SPACE_AND_BACKSLASH, "ARG_3"),168Arrays.asList(SPACE_AND_BACKSLASH + BACKSLASH, "ARG_3")),169170// allowAmbiguousCommands = false, WITH application supplied double-quotes around the argument171// The argument has surrounding quotes so does not need further quoting.172// However, for exe commands, the final backslash must not be allowed to turn the quote173// into a literal instead of closing the quote.174new CMD(false,175ECHO_JAVA_ARGS,176Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_11"),177Arrays.asList(SPACE_AND_BACKSLASH, "ARG_11")),178new CMD(false,179ECHO_CMD_ARGS,180Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_12"),181Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_12")),182new CMD(false,183ECHO_VBS_ARGS,184Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_13"),185Arrays.asList(SPACE_AND_BACKSLASH + BACKSLASH, "ARG_13")),186187// Legacy mode tests; allowAmbiguousCommands = true; no application supplied quotes188// The space in the argument requires it to be quoted, the final backslash189// must not be allowed to turn the quote that is added into a literal190// instead of closing the quote.191new CMD(true,192ECHO_JAVA_ARGS,193Arrays.asList(SPACE_AND_BACKSLASH, "ARG_21"),194Arrays.asList(SPACE_AND_BACKSLASH, "ARG_21")),195new CMD(true,196ECHO_CMD_ARGS,197Arrays.asList(SPACE_AND_BACKSLASH, "ARG_22"),198Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + BACKSLASH + DOUBLE_QUOTE, "ARG_22")),199new CMD(true,200ECHO_VBS_ARGS,201Arrays.asList(SPACE_AND_BACKSLASH, "ARG_23"),202Arrays.asList(SPACE_AND_BACKSLASH + BACKSLASH, "ARG_23")),203204// allowAmbiguousCommands = true, WITH application supplied double-quotes around the argument205// The argument has surrounding quotes so does not need further quoting.206// The backslash before the final quote is ignored and is interpreted differently for each command.207new CMD(true,208ECHO_JAVA_ARGS,209Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_31"),210Arrays.asList("SPACE AND BACKSLASH\" ARG_31")),211new CMD(true,212ECHO_CMD_ARGS,213Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_32"),214Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_32")),215new CMD(true,216ECHO_VBS_ARGS,217Arrays.asList(DOUBLE_QUOTE + SPACE_AND_BACKSLASH + DOUBLE_QUOTE, "ARG_33"),218Arrays.asList(SPACE_AND_BACKSLASH, "ARG_33"))219);220221/**222* Common function to Invoke a process with the commands and check the result.223*224* @param cmd a CMD test case with arguments, allowAmbiguousCommands mode, and expected output225*/226private static void testCommand(CMD cmd) throws Exception {227System.setProperty(AMBIGUOUS_PROP_NAME, Boolean.toString(cmd.allowAmbiguous));228List<String> actual = null;229List<String> arguments = new ArrayList<>(cmd.command);230arguments.addAll(cmd.arguments);231try {232// Launch the process and wait for termination233ProcessBuilder pb = new ProcessBuilder(arguments);234Process process = pb.start();235try (InputStream is = process.getInputStream()) {236String str = readAllBytesAsString(is);237str = str.replace("\r", "");238actual = Arrays.asList(str.split("\n"));239} catch (IOException ioe) {240throw new RuntimeException(ioe.getMessage(), ioe);241}242int exitCode = process.waitFor();243if (exitCode != 0) {244actual = new ArrayList(actual);245actual.add("Exit code: " + exitCode);246}247} catch (IOException ioe) {248actual = Arrays.asList(ioe.getMessage().replace(arguments.get(0), "CMD"));249} catch (Exception ex) {250actual = Arrays.asList(ex.getMessage()); // Use exception message as output251}252if (!Objects.equals(actual, cmd.expected)) {253System.out.println("Invoking(" + cmd.allowAmbiguous + "): " + arguments);254if (actual.size() != cmd.expected.size()) {255System.out.println("Args Length: actual: " + actual.size() + " expected: " + cmd.expected.size());256}257System.out.println("Actual: " + actual);258System.out.println("Expected: " + cmd.expected);259System.out.println();260throw new RuntimeException("Unexpected output");261}262}263264/**265* Private method to readAllBytes as a String.266* (InputStream.readAllBytes is not supported by the JDK until 9)267* @param is an InputStream268* @return a String with the contents269* @throws IOException if an error occurs270*/271private static String readAllBytesAsString(InputStream is) throws IOException {272final int BUF_SIZE = 8192;273byte[] bytes = new byte[BUF_SIZE];274int off = 0;275int len;276while ((len = is.read(bytes, off, bytes.length - off)) > 0) {277off += len;278if (off >= bytes.length) {279// no space in buffer, reallocate larger280bytes = Arrays.copyOf(bytes, bytes.length + BUF_SIZE);281}282}283return new String(bytes, 0, off, Charset.defaultCharset());284}285286/**287* Initialize .cmd and .vbs scripts.288*289* @throws Error if an exception occurs290*/291private static void createFiles() throws IOException {292Files.write(Paths.get(ECHO_CMD_PATH), EchoArgumentsCmd.getBytes(StandardCharsets.UTF_8));293Files.write(Paths.get(ECHO_VBS_PATH), EchoArgumentsVbs.getBytes(StandardCharsets.UTF_8));294}295296/**297* Self contained .cmd to echo each argument on a separate line.298*/299static final String EchoArgumentsCmd = "@echo off\n" +300"set p1=\n" +301"set p2=\n" +302"\n" +303"if not [%1]==[] set p1=%1\n" +304"if not [%2]==[] set p2=%2\n" +305"if not [%3]==[] set p3=%3\n" +306"if defined p1 echo %p1%\n" +307"if defined p2 echo %p2%\n" +308"if defined p3 echo %p3%\n" +309"exit /b 0\n";310311312/**313* Self contained .vbs to echo each argument on a separate line.314*/315static final String EchoArgumentsVbs = "Option Explicit\n" +316"Dim arg\n" +317"for each arg in WScript.Arguments\n" +318" WScript.StdOut.WriteLine(arg)\n" +319"Next\n";320}321322323