View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.commons.exec;
21  
22  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
23  import static org.junit.jupiter.api.Assertions.assertEquals;
24  import static org.junit.jupiter.api.Assertions.assertFalse;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  
28  import java.io.File;
29  import java.util.HashMap;
30  import java.util.Map;
31  
32  import org.apache.commons.exec.util.StringUtils;
33  import org.junit.jupiter.api.Test;
34  
35  /**
36   */
37  class CommandLineTest {
38  
39      @Test
40      void testAddArgument() {
41          final CommandLine cmdl = new CommandLine("test");
42          cmdl.addArgument("foo");
43          cmdl.addArgument("bar");
44          assertEquals("[test, foo, bar]", cmdl.toString());
45          assertArrayEquals(new String[] { "test", "foo", "bar" }, cmdl.toStrings());
46      }
47  
48      @Test
49      void testAddArguments() {
50          final CommandLine cmdl = new CommandLine("test");
51          cmdl.addArguments("foo bar");
52          assertEquals("[test, foo, bar]", cmdl.toString());
53          assertArrayEquals(new String[] { "test", "foo", "bar" }, cmdl.toStrings());
54      }
55  
56      @Test
57      void testAddArgumentsArray() {
58          final CommandLine cmdl = new CommandLine("test");
59          cmdl.addArguments(new String[] { "foo", "bar" });
60          assertEquals("[test, foo, bar]", cmdl.toString());
61          assertArrayEquals(new String[] { "test", "foo", "bar" }, cmdl.toStrings());
62      }
63  
64      @Test
65      void testAddArgumentsArrayNull() {
66          final CommandLine cmdl = new CommandLine("test");
67          cmdl.addArguments((String[]) null);
68          assertEquals("[test]", cmdl.toString());
69          assertArrayEquals(new String[] { "test" }, cmdl.toStrings());
70      }
71  
72      @Test
73      void testAddArgumentsWithQuotes() {
74          final CommandLine cmdl = new CommandLine("test");
75          cmdl.addArguments("'foo' \"bar\"");
76          assertEquals("[test, foo, bar]", cmdl.toString());
77          assertArrayEquals(new String[] { "test", "foo", "bar" }, cmdl.toStrings());
78      }
79  
80      @Test
81      void testAddArgumentsWithQuotesAndSpaces() {
82          final CommandLine cmdl = new CommandLine("test");
83          cmdl.addArguments("'fo o' \"ba r\"");
84          assertEquals("[test, \"fo o\", \"ba r\"]", cmdl.toString());
85          assertArrayEquals(new String[] { "test", "\"fo o\"", "\"ba r\"" }, cmdl.toStrings());
86      }
87  
88      @Test
89      void testAddArgumentWithBothQuotes() {
90          final CommandLine cmdl = new CommandLine("test");
91          assertThrows(IllegalArgumentException.class, () -> cmdl.addArgument("b\"a'r"));
92      }
93  
94      @Test
95      void testAddArgumentWithQuote() {
96          final CommandLine cmdl = new CommandLine("test");
97          cmdl.addArgument("foo");
98          cmdl.addArgument("ba\"r");
99          assertEquals("[test, foo, 'ba\"r']", cmdl.toString());
100         assertArrayEquals(new String[] { "test", "foo", "'ba\"r'" }, cmdl.toStrings());
101     }
102 
103     @Test
104     void testAddArgumentWithQuotesAround() {
105         final CommandLine cmdl = new CommandLine("test");
106         cmdl.addArgument("\'foo\'");
107         cmdl.addArgument("\"bar\"");
108         cmdl.addArgument("\"fe z\"");
109         assertEquals("[test, foo, bar, \"fe z\"]", cmdl.toString());
110         assertArrayEquals(new String[] { "test", "foo", "bar", "\"fe z\"" }, cmdl.toStrings());
111     }
112 
113     @Test
114     void testAddArgumentWithSingleQuote() {
115         final CommandLine cmdl = new CommandLine("test");
116 
117         cmdl.addArgument("foo");
118         cmdl.addArgument("ba'r");
119         assertEquals("[test, foo, \"ba'r\"]", cmdl.toString());
120         assertArrayEquals(new String[] { "test", "foo", "\"ba\'r\"" }, cmdl.toStrings());
121     }
122 
123     @Test
124     void testAddArgumentWithSpace() {
125         final CommandLine cmdl = new CommandLine("test");
126         cmdl.addArgument("foo");
127         cmdl.addArgument("ba r");
128         assertEquals("[test, foo, \"ba r\"]", cmdl.toString());
129         assertArrayEquals(new String[] { "test", "foo", "\"ba r\"" }, cmdl.toStrings());
130     }
131 
132     @Test
133     void testAddNullArgument() {
134         final CommandLine cmdl = new CommandLine("test");
135 
136         cmdl.addArgument(null);
137         assertEquals("[test]", cmdl.toString());
138         assertArrayEquals(new String[] { "test" }, cmdl.toStrings());
139     }
140 
141     /**
142      * A little example how to add two command line arguments in one line, e.g. to make commenting out some options less error-prone.
143      */
144     @Test
145     void testAddTwoArguments() {
146 
147         final CommandLine userAddCL1 = new CommandLine("useradd");
148         userAddCL1.addArgument("-g");
149         userAddCL1.addArgument("tomcat");
150         userAddCL1.addArgument("foo");
151 
152         final CommandLine userAddCL2 = new CommandLine("useradd");
153         userAddCL2.addArgument("-g").addArgument("tomcat");
154         userAddCL2.addArgument("foo");
155 
156         assertEquals(userAddCL1.toString(), userAddCL2.toString());
157     }
158 
159     /**
160      * Test expanding the command line based on a user-supplied map.
161      */
162     @Test
163     void testCommandLineParsingWithExpansion1() {
164 
165         CommandLine cmdl;
166 
167         final Map<String, Object> substitutionMap = new HashMap<>();
168         substitutionMap.put("JAVA_HOME", "/usr/local/java");
169         substitutionMap.put("appMainClass", "foo.bar.Main");
170         substitutionMap.put("file1", new File("./pom.xml"));
171         substitutionMap.put("file2", new File(".\\temp\\READ ME.txt"));
172 
173         final Map<String, String> incompleteMap = new HashMap<>();
174         incompleteMap.put("JAVA_HOME", "/usr/local/java");
175 
176         // do not pass substitution map
177         cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass}");
178         assertTrue(cmdl.getExecutable().startsWith("${JAVA_HOME}"));
179         assertArrayEquals(new String[] { "${appMainClass}" }, cmdl.getArguments());
180 
181         // pass arguments with an empty map
182         cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass}", new HashMap<>());
183         assertTrue(cmdl.getExecutable().startsWith("${JAVA_HOME}"));
184         assertArrayEquals(new String[] { "${appMainClass}" }, cmdl.getArguments());
185 
186         // pass a complete substitution map
187         cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass}", substitutionMap);
188         assertFalse(cmdl.getExecutable().contains("${JAVA_HOME}"));
189         assertTrue(cmdl.getExecutable().indexOf("local") > 0);
190         assertArrayEquals(new String[] { "foo.bar.Main" }, cmdl.getArguments());
191 
192         // pass an incomplete substitution map resulting in unresolved variables
193         cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass}", incompleteMap);
194         assertFalse(cmdl.getExecutable().contains("${JAVA_HOME}"));
195         assertTrue(cmdl.getExecutable().indexOf("local") > 0);
196         assertArrayEquals(new String[] { "${appMainClass}" }, cmdl.getArguments());
197 
198         // pass a file
199         cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass} ${file1} ${file2}", substitutionMap);
200         assertFalse(cmdl.getExecutable().contains("${file}"));
201     }
202 
203     /**
204      * Test expanding the command line based on a user-supplied map. The main goal of the test is to set up a command line using macros and reuse it multiple
205      * times.
206      */
207     @Test
208     void testCommandLineParsingWithExpansion2() {
209 
210         CommandLine cmdl;
211         String[] result;
212 
213         // build the user supplied parameters
214         final Map<String, String> substitutionMap = new HashMap<>();
215         substitutionMap.put("JAVA_HOME", "C:\\Programme\\jdk1.5.0_12");
216         substitutionMap.put("appMainClass", "foo.bar.Main");
217 
218         // build the command line
219         cmdl = new CommandLine("${JAVA_HOME}\\bin\\java");
220         cmdl.addArgument("-class");
221         cmdl.addArgument("${appMainClass}");
222         cmdl.addArgument("${file}");
223 
224         // build the first command line
225         substitutionMap.put("file", "C:\\Document And Settings\\documents\\432431.pdf");
226         cmdl.setSubstitutionMap(substitutionMap);
227         result = cmdl.toStrings();
228 
229         // verify the first command line
230         // please note - the executable argument is changed to using platform specific file separator char
231         // whereas all other variable substitution are not touched
232         assertEquals(StringUtils.fixFileSeparatorChar("C:\\Programme\\jdk1.5.0_12\\bin\\java"), result[0]);
233         assertEquals("-class", result[1]);
234         assertEquals("foo.bar.Main", result[2]);
235         assertEquals("\"C:\\Document And Settings\\documents\\432431.pdf\"", result[3]);
236 
237         // verify the first command line again but by
238         // accessing the executable and arguments directly
239         final String executable = cmdl.getExecutable();
240         final String[] arguments = cmdl.getArguments();
241         assertEquals(StringUtils.fixFileSeparatorChar("C:\\Programme\\jdk1.5.0_12\\bin\\java"), executable);
242         assertEquals("-class", arguments[0]);
243         assertEquals("foo.bar.Main", arguments[1]);
244         assertEquals("\"C:\\Document And Settings\\documents\\432431.pdf\"", arguments[2]);
245 
246         // build the second command line with updated parameters resulting in a different command line
247         substitutionMap.put("file", "C:\\Document And Settings\\documents\\432432.pdf");
248         result = cmdl.toStrings();
249         assertEquals(StringUtils.fixFileSeparatorChar("C:\\Programme\\jdk1.5.0_12\\bin\\java"), result[0]);
250         assertEquals("-class", result[1]);
251         assertEquals("foo.bar.Main", result[2]);
252         assertEquals("\"C:\\Document And Settings\\documents\\432432.pdf\"", result[3]);
253     }
254 
255     @Test
256     void testCommandLineParsingWithExpansion3() {
257         final CommandLine cmdl = CommandLine.parse("AcroRd32.exe");
258         cmdl.addArgument("/p");
259         cmdl.addArgument("/h");
260         cmdl.addArgument("${file}", false);
261         final Map<String, String> params = new HashMap<>();
262         params.put("file", "C:\\Document And Settings\\documents\\432432.pdf");
263         cmdl.setSubstitutionMap(params);
264         final String[] result = cmdl.toStrings();
265         assertEquals("AcroRd32.exe", result[0]);
266         assertEquals("/p", result[1]);
267         assertEquals("/h", result[2]);
268         assertEquals("C:\\Document And Settings\\documents\\432432.pdf", result[3]);
269 
270     }
271 
272     /**
273      * Create a command line with pre-quoted strings to test SANDBOX-192, e.g. "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC",
274      * "\"-XX:ParallelGCThreads=2\""
275      */
276     @Test
277     void testComplexAddArgument() {
278         final CommandLine cmdl = new CommandLine("runMemorySud.cmd");
279         cmdl.addArgument("10", false);
280         cmdl.addArgument("30", false);
281         cmdl.addArgument("-XX:+UseParallelGC", false);
282         cmdl.addArgument("\"-XX:ParallelGCThreads=2\"", false);
283         assertArrayEquals(new String[] { "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\"" }, cmdl.toStrings());
284     }
285 
286     /**
287      * Create a command line with pre-quoted strings to test SANDBOX-192, e.g. "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC",
288      * "\"-XX:ParallelGCThreads=2\""
289      */
290     @Test
291     void testComplexAddArguments1() {
292         final CommandLine cmdl = new CommandLine("runMemorySud.cmd");
293         cmdl.addArguments(new String[] { "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\"" }, false);
294         assertArrayEquals(new String[] { "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\"" }, cmdl.toStrings());
295     }
296 
297     /**
298      * Create a command line with pre-quoted strings to test SANDBOX-192, e.g. "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC",
299      * "\"-XX:ParallelGCThreads=2\"" Please note that we re forced to add additional single quotes to get the test working - don't know if this is a bug or a
300      * feature.
301      */
302     @Test
303     void testComplexAddArguments2() {
304         final CommandLine cmdl = new CommandLine("runMemorySud.cmd");
305         cmdl.addArguments("10 30 -XX:+UseParallelGC '\"-XX:ParallelGCThreads=2\"'", false);
306         assertArrayEquals(new String[] { "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\"" }, cmdl.toStrings());
307     }
308 
309     @Test
310     void testCopyConstructor() {
311         final Map<String, String> map = new HashMap<>();
312         map.put("bar", "bar");
313         final CommandLine other = new CommandLine("test");
314         other.addArgument("foo");
315         other.setSubstitutionMap(map);
316 
317         final CommandLine cmdl = new CommandLine(other);
318         assertEquals(other.getExecutable(), cmdl.getExecutable());
319         assertArrayEquals(other.getArguments(), cmdl.getArguments());
320         assertEquals(other.isFile(), cmdl.isFile());
321         assertEquals(other.getSubstitutionMap(), cmdl.getSubstitutionMap());
322 
323     }
324 
325     @Test
326     void testExecutable() {
327         final CommandLine cmdl = new CommandLine("test");
328         assertEquals("[test]", cmdl.toString());
329         assertArrayEquals(new String[] { "test" }, cmdl.toStrings());
330         assertEquals("test", cmdl.getExecutable());
331         assertTrue(cmdl.getArguments().length == 0);
332     }
333 
334     @Test
335     void testExecutableWhitespaceString() {
336         assertThrows(IllegalArgumentException.class, () -> new CommandLine("   "));
337     }
338 
339     @Test
340     void testExecutableZeroLengthString() {
341         assertThrows(IllegalArgumentException.class, () -> new CommandLine(""));
342     }
343 
344     @Test
345     void testNullExecutable() {
346         assertThrows(NullPointerException.class, () -> new CommandLine((String) null));
347     }
348 
349     @Test
350     void testParseCommandLine() {
351         final CommandLine cmdl = CommandLine.parse("test foo bar");
352         assertEquals("[test, foo, bar]", cmdl.toString());
353         assertArrayEquals(new String[] { "test", "foo", "bar" }, cmdl.toStrings());
354     }
355 
356     @Test
357     void testParseCommandLineWithNull() {
358         assertThrows(IllegalArgumentException.class, () -> CommandLine.parse(null));
359     }
360 
361     @Test
362     void testParseCommandLineWithOnlyWhitespace() {
363         assertThrows(IllegalArgumentException.class, () -> CommandLine.parse("  "));
364     }
365 
366     @Test
367     void testParseCommandLineWithQuotes() {
368         final CommandLine cmdl = CommandLine.parse("test \"foo\" \'ba r\'");
369         assertEquals("[test, foo, \"ba r\"]", cmdl.toString());
370         assertArrayEquals(new String[] { "test", "foo", "\"ba r\"" }, cmdl.toStrings());
371     }
372 
373     @Test
374     void testParseCommandLineWithUnevenQuotes() {
375         assertThrows(IllegalArgumentException.class, () -> CommandLine.parse("test \"foo bar"), "IllegalArgumentException must be thrown due to uneven quotes");
376     }
377 
378     /**
379      * A command line parsing puzzle from Tino Schoellhorn - ImageMagix expects a "500x>" parameter (including quotes) and it is simply not possible to do that
380      * without adding a space, e.g. "500x> ".
381      */
382     @Test
383     void testParseComplexCommandLine1() {
384         final Map<String, String> substitutionMap = new HashMap<>();
385         substitutionMap.put("in", "source.jpg");
386         substitutionMap.put("out", "target.jpg");
387         final CommandLine cmdl = CommandLine.parse("cmd /C convert ${in} -resize \"\'500x> \'\" ${out}", substitutionMap);
388         assertEquals("[cmd, /C, convert, source.jpg, -resize, \"500x> \", target.jpg]", cmdl.toString());
389     }
390 
391     /**
392      * Another command line parsing puzzle from Kai Hu - as far as I understand it there is no way to express that in a one-line command string.
393      */
394     @Test
395     void testParseComplexCommandLine2() {
396         final String commandLine = "./script/jrake cruise:publish_installers INSTALLER_VERSION=unstable_2_1 "
397                 + "INSTALLER_PATH=\"/var/lib/ cruise-agent/installers\" INSTALLER_DOWNLOAD_SERVER=\'something\' WITHOUT_HELP_DOC=true";
398         final CommandLine cmdl = CommandLine.parse(commandLine);
399         final String[] args = cmdl.getArguments();
400         assertEquals(args[0], "cruise:publish_installers");
401         assertEquals(args[1], "INSTALLER_VERSION=unstable_2_1");
402         // assertEquals(args[2], "INSTALLER_PATH=\"/var/lib/ cruise-agent/installers\"");
403         // assertEquals(args[3], "INSTALLER_DOWNLOAD_SERVER='something'");
404         assertEquals(args[4], "WITHOUT_HELP_DOC=true");
405     }
406 
407     /**
408      * Test the following command line:
409      * <pre>
410      * cmd.exe /C c:\was51\Web Sphere\AppServer\bin\versionInfo.bat
411      * </pre>
412      */
413     @Test
414     void testParseRealLifeCommandLine() {
415 
416         final String commandLine = "cmd.exe /C \"c:\\was51\\Web Sphere\\AppServer\\bin\\versionInfo.bat\"";
417 
418         final CommandLine cmdl = CommandLine.parse(commandLine);
419         final String[] args = cmdl.getArguments();
420         assertEquals("/C", args[0]);
421         assertEquals("\"c:\\was51\\Web Sphere\\AppServer\\bin\\versionInfo.bat\"", args[1]);
422     }
423 
424     /**
425      * Test the toString() method.
426      *
427      * @throws Exception the test failed
428      */
429     @Test
430     void testToString() throws Exception {
431         CommandLine cmdl;
432         final Map<String, String> params = new HashMap<>();
433 
434         // use no arguments
435         cmdl = CommandLine.parse("AcroRd32.exe", params);
436         assertEquals("[AcroRd32.exe]", cmdl.toString());
437 
438         // use an argument containing spaces
439         params.put("file", "C:\\Document And Settings\\documents\\432432.pdf");
440         cmdl = CommandLine.parse("AcroRd32.exe /p /h '${file}'", params);
441         assertEquals("[AcroRd32.exe, /p, /h, \"C:\\Document And Settings\\documents\\432432.pdf\"]", cmdl.toString());
442 
443         // use an argument without spaces
444         params.put("file", "C:\\documents\\432432.pdf");
445         cmdl = CommandLine.parse("AcroRd32.exe /p /h '${file}'", params);
446         assertEquals("[AcroRd32.exe, /p, /h, C:\\documents\\432432.pdf]", cmdl.toString());
447     }
448 
449     /**
450      * Test that toString() produces output that is useful for troubleshooting.
451      *
452      * @throws Exception the test failed
453      */
454     @Test
455     void testToStringTroubleshooting() throws Exception {
456         // On HP-UX quotes handling leads to errors,
457         // also usage of quotes isn't mandatory on other platforms too
458         // so it probably should work correctly either way.
459         final CommandLine cmd1 = new CommandLine("sh").addArgument("-c").addArgument("echo 1", false);
460         final CommandLine cmd2 = new CommandLine("sh").addArgument("-c").addArgument("echo").addArgument("1");
461         assertTrue(!cmd1.toString().equals(cmd2.toString()), "toString() is useful for troubleshooting");
462     }
463 
464 }