View Javadoc
1   package org.codehaus.plexus.util.cli.shell;
2   
3   /*
4    * Copyright The Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.io.File;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.List;
23  
24  import org.codehaus.plexus.util.StringUtils;
25  
26  /**
27   * Class that abstracts the Shell functionality, with subclasses for shells that behave particularly, like
28   * <ul>
29   * <li><code>command.com</code></li>
30   * <li><code>cmd.exe</code></li>
31   * </ul>
32   *
33   * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a>
34   * @since 1.2
35   *
36   */
37  public class Shell implements Cloneable {
38      private static final char[] DEFAULT_QUOTING_TRIGGER_CHARS = {' '};
39  
40      private String shellCommand;
41  
42      private List<String> shellArgs = new ArrayList<String>();
43  
44      private boolean quotedArgumentsEnabled = true;
45  
46      private boolean unconditionallyQuote = false;
47  
48      private String executable;
49  
50      private String workingDir;
51  
52      private boolean quotedExecutableEnabled = true;
53  
54      private boolean doubleQuotedArgumentEscaped = false;
55  
56      private boolean singleQuotedArgumentEscaped = false;
57  
58      private boolean doubleQuotedExecutableEscaped = false;
59  
60      private boolean singleQuotedExecutableEscaped = false;
61  
62      private char argQuoteDelimiter = '\"';
63  
64      private char exeQuoteDelimiter = '\"';
65  
66      private String argumentEscapePattern = "\\%s";
67  
68      /**
69       * Toggle unconditional quoting
70       *
71       * @param unconditionallyQuote see name
72       */
73      public void setUnconditionalQuoting(boolean unconditionallyQuote) {
74          this.unconditionallyQuote = unconditionallyQuote;
75      }
76  
77      /**
78       * Set the command to execute the shell (eg. COMMAND.COM, /bin/bash,...)
79       *
80       * @param shellCommand see name
81       */
82      public void setShellCommand(String shellCommand) {
83          this.shellCommand = shellCommand;
84      }
85  
86      /**
87       * Get the command to execute the shell
88       *
89       * @return the command
90       */
91      public String getShellCommand() {
92          return shellCommand;
93      }
94  
95      /**
96       * Set the shell arguments when calling a command line (not the executable arguments) (eg. /X /C for CMD.EXE)
97       *
98       * @param shellArgs see name
99       */
100     public void setShellArgs(String[] shellArgs) {
101         this.shellArgs.clear();
102         this.shellArgs.addAll(Arrays.asList(shellArgs));
103     }
104 
105     /**
106      * @return the shell arguments
107      */
108     public String[] getShellArgs() {
109         if ((shellArgs == null) || shellArgs.isEmpty()) {
110             return null;
111         } else {
112             return shellArgs.toArray(new String[0]);
113         }
114     }
115 
116     /**
117      * Get the command line for the provided executable and arguments in this shell
118      *
119      * @param executable executable that the shell has to call
120      * @param arguments arguments for the executable, not the shell
121      * @return List with one String object with executable and arguments quoted as needed
122      */
123     public List<String> getCommandLine(String executable, String[] arguments) {
124         return getRawCommandLine(executable, arguments);
125     }
126 
127     protected String quoteOneItem(String inputString, boolean isExecutable) {
128         char[] escapeChars = getEscapeChars(isSingleQuotedExecutableEscaped(), isDoubleQuotedExecutableEscaped());
129         return StringUtils.quoteAndEscape(
130                 inputString,
131                 isExecutable ? getExecutableQuoteDelimiter() : getArgumentQuoteDelimiter(),
132                 escapeChars,
133                 getQuotingTriggerChars(),
134                 '\\',
135                 unconditionallyQuote);
136     }
137 
138     protected List<String> getRawCommandLine(String executable, String[] arguments) {
139         List<String> commandLine = new ArrayList<String>();
140         StringBuilder sb = new StringBuilder();
141 
142         if (executable != null) {
143             String preamble = getExecutionPreamble();
144             if (preamble != null) {
145                 sb.append(preamble);
146             }
147 
148             if (isQuotedExecutableEnabled()) {
149                 sb.append(quoteOneItem(getOriginalExecutable(), true));
150             } else {
151                 sb.append(getExecutable());
152             }
153         }
154         for (String argument : arguments) {
155             if (sb.length() > 0) {
156                 sb.append(" ");
157             }
158 
159             if (isQuotedArgumentsEnabled()) {
160                 sb.append(quoteOneItem(argument, false));
161             } else {
162                 sb.append(argument);
163             }
164         }
165 
166         commandLine.add(sb.toString());
167 
168         return commandLine;
169     }
170 
171     protected char[] getQuotingTriggerChars() {
172         return DEFAULT_QUOTING_TRIGGER_CHARS;
173     }
174 
175     protected String getExecutionPreamble() {
176         return null;
177     }
178 
179     protected char[] getEscapeChars(boolean includeSingleQuote, boolean includeDoubleQuote) {
180         StringBuilder buf = new StringBuilder(2);
181         if (includeSingleQuote) {
182             buf.append('\'');
183         }
184 
185         if (includeDoubleQuote) {
186             buf.append('\"');
187         }
188 
189         char[] result = new char[buf.length()];
190         buf.getChars(0, buf.length(), result, 0);
191 
192         return result;
193     }
194 
195     protected boolean isDoubleQuotedArgumentEscaped() {
196         return doubleQuotedArgumentEscaped;
197     }
198 
199     protected boolean isSingleQuotedArgumentEscaped() {
200         return singleQuotedArgumentEscaped;
201     }
202 
203     protected boolean isDoubleQuotedExecutableEscaped() {
204         return doubleQuotedExecutableEscaped;
205     }
206 
207     protected boolean isSingleQuotedExecutableEscaped() {
208         return singleQuotedExecutableEscaped;
209     }
210 
211     protected void setArgumentQuoteDelimiter(char argQuoteDelimiter) {
212         this.argQuoteDelimiter = argQuoteDelimiter;
213     }
214 
215     protected char getArgumentQuoteDelimiter() {
216         return argQuoteDelimiter;
217     }
218 
219     protected void setExecutableQuoteDelimiter(char exeQuoteDelimiter) {
220         this.exeQuoteDelimiter = exeQuoteDelimiter;
221     }
222 
223     protected char getExecutableQuoteDelimiter() {
224         return exeQuoteDelimiter;
225     }
226 
227     protected void setArgumentEscapePattern(String argumentEscapePattern) {
228         this.argumentEscapePattern = argumentEscapePattern;
229     }
230 
231     protected String getArgumentEscapePattern() {
232         return argumentEscapePattern;
233     }
234 
235     /**
236      * Get the full command line to execute, including shell command, shell arguments, executable and executable
237      * arguments
238      *
239      * @param arguments arguments for the executable, not the shell
240      * @return List of String objects, whose array version is suitable to be used as argument of
241      *         Runtime.getRuntime().exec()
242      */
243     public List<String> getShellCommandLine(String[] arguments) {
244 
245         List<String> commandLine = new ArrayList<String>();
246 
247         if (getShellCommand() != null) {
248             commandLine.add(getShellCommand());
249         }
250 
251         if (getShellArgs() != null) {
252             commandLine.addAll(getShellArgsList());
253         }
254 
255         commandLine.addAll(getCommandLine(getOriginalExecutable(), arguments));
256 
257         return commandLine;
258     }
259 
260     public List<String> getShellArgsList() {
261         return shellArgs;
262     }
263 
264     public void addShellArg(String arg) {
265         shellArgs.add(arg);
266     }
267 
268     public void setQuotedArgumentsEnabled(boolean quotedArgumentsEnabled) {
269         this.quotedArgumentsEnabled = quotedArgumentsEnabled;
270     }
271 
272     public boolean isQuotedArgumentsEnabled() {
273         return quotedArgumentsEnabled;
274     }
275 
276     public void setQuotedExecutableEnabled(boolean quotedExecutableEnabled) {
277         this.quotedExecutableEnabled = quotedExecutableEnabled;
278     }
279 
280     public boolean isQuotedExecutableEnabled() {
281         return quotedExecutableEnabled;
282     }
283 
284     /**
285      *
286      * @param executable Sets the executable to run.
287      */
288     public void setExecutable(String executable) {
289         if ((executable == null) || (executable.length() == 0)) {
290             return;
291         }
292         this.executable = executable.replace('/', File.separatorChar).replace('\\', File.separatorChar);
293     }
294 
295     public String getExecutable() {
296         return executable;
297     }
298 
299     /**
300      * @param path Sets execution directory.
301      */
302     public void setWorkingDirectory(String path) {
303         if (path != null) {
304             workingDir = path;
305         }
306     }
307 
308     /**
309      * @param workingDir Sets execution directory.
310      */
311     public void setWorkingDirectory(File workingDir) {
312         if (workingDir != null) {
313             this.workingDir = workingDir.getAbsolutePath();
314         }
315     }
316 
317     public File getWorkingDirectory() {
318         return workingDir == null ? null : new File(workingDir);
319     }
320 
321     public String getWorkingDirectoryAsString() {
322         return workingDir;
323     }
324 
325     public void clearArguments() {
326         shellArgs.clear();
327     }
328 
329     @Override
330     public Object clone() {
331         Shell shell = new Shell();
332         shell.setExecutable(getExecutable());
333         shell.setWorkingDirectory(getWorkingDirectory());
334         shell.setShellArgs(getShellArgs());
335         return shell;
336     }
337 
338     public String getOriginalExecutable() {
339         return executable;
340     }
341 
342     public List<String> getOriginalCommandLine(String executable, String[] arguments) {
343         return getRawCommandLine(executable, arguments);
344     }
345 
346     protected void setDoubleQuotedArgumentEscaped(boolean doubleQuotedArgumentEscaped) {
347         this.doubleQuotedArgumentEscaped = doubleQuotedArgumentEscaped;
348     }
349 
350     protected void setDoubleQuotedExecutableEscaped(boolean doubleQuotedExecutableEscaped) {
351         this.doubleQuotedExecutableEscaped = doubleQuotedExecutableEscaped;
352     }
353 
354     protected void setSingleQuotedArgumentEscaped(boolean singleQuotedArgumentEscaped) {
355         this.singleQuotedArgumentEscaped = singleQuotedArgumentEscaped;
356     }
357 
358     protected void setSingleQuotedExecutableEscaped(boolean singleQuotedExecutableEscaped) {
359         this.singleQuotedExecutableEscaped = singleQuotedExecutableEscaped;
360     }
361 }