1 /* Quoting for a system command.
2 Copyright (C) 2012-2018 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2012.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17
18 #include <config.h>
19
20 /* Specification. */
21 #include "system-quote.h"
22
23 #include <stdbool.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "sh-quote.h"
28 #include "xalloc.h"
29
30 #if defined _WIN32 && ! defined __CYGWIN__
31
32 /* The native Windows CreateProcess() function interprets characters like
33 ' ', '\t', '\\', '"' (but not '<' and '>') in a special way:
34 - Space and tab are interpreted as delimiters. They are not treated as
35 delimiters if they are surrounded by double quotes: "...".
36 - Unescaped double quotes are removed from the input. Their only effect is
37 that within double quotes, space and tab are treated like normal
38 characters.
39 - Backslashes not followed by double quotes are not special.
40 - But 2*n+1 backslashes followed by a double quote become
41 n backslashes followed by a double quote (n >= 0):
42 \" -> "
43 \\\" -> \"
44 \\\\\" -> \\"
45 - '*', '?' characters may get expanded through wildcard expansion in the
46 callee: By default, in the callee, the initialization code before main()
47 takes the result of GetCommandLine(), wildcard-expands it, and passes it
48 to main(). The exceptions to this rule are:
49 - programs that inspect GetCommandLine() and ignore argv,
50 - mingw programs that have a global variable 'int _CRT_glob = 0;',
51 - Cygwin programs, when invoked from a Cygwin program.
52 */
53 # define SHELL_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037*?"
54 # define SHELL_SPACE_CHARS " \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
55
56 /* Copies the quoted string to p and returns the number of bytes needed.
57 If p is non-NULL, there must be room for system_quote_length (string)
58 bytes at p. */
59 static size_t
windows_createprocess_quote(char * p,const char * string)60 windows_createprocess_quote (char *p, const char *string)
61 {
62 size_t len = strlen (string);
63 bool quote_around =
64 (len == 0 || strpbrk (string, SHELL_SPECIAL_CHARS) != NULL);
65 size_t backslashes = 0;
66 size_t i = 0;
67 # define STORE(c) \
68 do \
69 { \
70 if (p != NULL) \
71 p[i] = (c); \
72 i++; \
73 } \
74 while (0)
75
76 if (quote_around)
77 STORE ('"');
78 for (; len > 0; string++, len--)
79 {
80 char c = *string;
81
82 if (c == '"')
83 {
84 size_t j;
85
86 for (j = backslashes + 1; j > 0; j--)
87 STORE ('\\');
88 }
89 STORE (c);
90 if (c == '\\')
91 backslashes++;
92 else
93 backslashes = 0;
94 }
95 if (quote_around)
96 {
97 size_t j;
98
99 for (j = backslashes; j > 0; j--)
100 STORE ('\\');
101 STORE ('"');
102 }
103 # undef STORE
104 return i;
105 }
106
107 /* The native Windows cmd.exe command interpreter also interprets:
108 - '\n', '\r' as a command terminator - no way to escape it,
109 - '<', '>' as redirections,
110 - '|' as pipe operator,
111 - '%var%' as a reference to the environment variable VAR (uppercase),
112 even inside quoted strings,
113 - '&' '[' ']' '{' '}' '^' '=' ';' '!' '\'' '+' ',' '`' '~' for other
114 purposes, according to
115 <https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/cmd.mspx?mfr=true>
116 We quote a string like '%var%' by putting the '%' characters outside of
117 double-quotes and the rest of the string inside double-quotes: %"var"%.
118 This is guaranteed to not be a reference to an environment variable.
119 */
120 # define CMD_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037!%&'*+,;<=>?[]^`{|}~"
121 # define CMD_FORBIDDEN_CHARS "\n\r"
122
123 /* Copies the quoted string to p and returns the number of bytes needed.
124 If p is non-NULL, there must be room for system_quote_length (string)
125 bytes at p. */
126 static size_t
windows_cmd_quote(char * p,const char * string)127 windows_cmd_quote (char *p, const char *string)
128 {
129 size_t len = strlen (string);
130 bool quote_around =
131 (len == 0 || strpbrk (string, CMD_SPECIAL_CHARS) != NULL);
132 size_t backslashes = 0;
133 size_t i = 0;
134 # define STORE(c) \
135 do \
136 { \
137 if (p != NULL) \
138 p[i] = (c); \
139 i++; \
140 } \
141 while (0)
142
143 if (quote_around)
144 STORE ('"');
145 for (; len > 0; string++, len--)
146 {
147 char c = *string;
148
149 if (c == '"')
150 {
151 size_t j;
152
153 for (j = backslashes + 1; j > 0; j--)
154 STORE ('\\');
155 }
156 if (c == '%')
157 {
158 size_t j;
159
160 for (j = backslashes; j > 0; j--)
161 STORE ('\\');
162 STORE ('"');
163 }
164 STORE (c);
165 if (c == '%')
166 STORE ('"');
167 if (c == '\\')
168 backslashes++;
169 else
170 backslashes = 0;
171 }
172 if (quote_around)
173 {
174 size_t j;
175
176 for (j = backslashes; j > 0; j--)
177 STORE ('\\');
178 STORE ('"');
179 }
180 return i;
181 }
182
183 #endif
184
185 size_t
system_quote_length(enum system_command_interpreter interpreter,const char * string)186 system_quote_length (enum system_command_interpreter interpreter,
187 const char *string)
188 {
189 switch (interpreter)
190 {
191 #if !(defined _WIN32 && ! defined __CYGWIN__)
192 case SCI_SYSTEM:
193 #endif
194 case SCI_POSIX_SH:
195 return shell_quote_length (string);
196
197 #if defined _WIN32 && ! defined __CYGWIN__
198 case SCI_WINDOWS_CREATEPROCESS:
199 return windows_createprocess_quote (NULL, string);
200
201 case SCI_SYSTEM:
202 case SCI_WINDOWS_CMD:
203 return windows_cmd_quote (NULL, string);
204 #endif
205
206 default:
207 /* Invalid interpreter. */
208 abort ();
209 }
210 }
211
212 char *
system_quote_copy(char * p,enum system_command_interpreter interpreter,const char * string)213 system_quote_copy (char *p,
214 enum system_command_interpreter interpreter,
215 const char *string)
216 {
217 switch (interpreter)
218 {
219 #if !(defined _WIN32 && ! defined __CYGWIN__)
220 case SCI_SYSTEM:
221 #endif
222 case SCI_POSIX_SH:
223 return shell_quote_copy (p, string);
224
225 #if defined _WIN32 && ! defined __CYGWIN__
226 case SCI_WINDOWS_CREATEPROCESS:
227 p += windows_createprocess_quote (p, string);
228 *p = '\0';
229 return p;
230
231 case SCI_SYSTEM:
232 case SCI_WINDOWS_CMD:
233 p += windows_cmd_quote (p, string);
234 *p = '\0';
235 return p;
236 #endif
237
238 default:
239 /* Invalid interpreter. */
240 abort ();
241 }
242 }
243
244 char *
system_quote(enum system_command_interpreter interpreter,const char * string)245 system_quote (enum system_command_interpreter interpreter,
246 const char *string)
247 {
248 switch (interpreter)
249 {
250 #if !(defined _WIN32 && ! defined __CYGWIN__)
251 case SCI_SYSTEM:
252 #endif
253 case SCI_POSIX_SH:
254 return shell_quote (string);
255
256 #if defined _WIN32 && ! defined __CYGWIN__
257 case SCI_WINDOWS_CREATEPROCESS:
258 case SCI_SYSTEM:
259 case SCI_WINDOWS_CMD:
260 {
261 size_t length = system_quote_length (interpreter, string);
262 char *quoted = XNMALLOC (length, char);
263 system_quote_copy (quoted, interpreter, string);
264 return quoted;
265 }
266 #endif
267
268 default:
269 /* Invalid interpreter. */
270 abort ();
271 }
272 }
273
274 char *
system_quote_argv(enum system_command_interpreter interpreter,char * const * argv)275 system_quote_argv (enum system_command_interpreter interpreter,
276 char * const *argv)
277 {
278 if (*argv != NULL)
279 {
280 char * const *argp;
281 size_t length;
282 char *command;
283 char *p;
284
285 length = 0;
286 for (argp = argv; ; )
287 {
288 length += system_quote_length (interpreter, *argp) + 1;
289 argp++;
290 if (*argp == NULL)
291 break;
292 }
293
294 command = XNMALLOC (length, char);
295
296 p = command;
297 for (argp = argv; ; )
298 {
299 p = system_quote_copy (p, interpreter, *argp);
300 argp++;
301 if (*argp == NULL)
302 break;
303 *p++ = ' ';
304 }
305 *p = '\0';
306
307 return command;
308 }
309 else
310 return xstrdup ("");
311 }
312