New module 'system-quote'.
[gnulib.git] / lib / system-quote.c
1 /* Quoting for a system command.
2    Copyright (C) 2012 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 <http://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 __WIN32__) && ! defined __CYGWIN__
31 /* The native Windows CreateProcess() function interprets characters like
32    ' ', '\t', '\\', '"' (but not '<' and '>') in a special way:
33    - Space and tab are interpreted as delimiters. They are not treated as
34      delimiters if they are surrounded by double quotes: "...".
35    - Unescaped double quotes are removed from the input. Their only effect is
36      that within double quotes, space and tab are treated like normal
37      characters.
38    - Backslashes not followed by double quotes are not special.
39    - But 2*n+1 backslashes followed by a double quote become
40      n backslashes followed by a double quote (n >= 0):
41        \" -> "
42        \\\" -> \"
43        \\\\\" -> \\"
44    - '*' characters may get expanded or lead to a failure with error code
45      ERROR_PATH_NOT_FOUND.
46  */
47 # 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*"
48 # 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"
49 /* The native Windows cmd.exe command interpreter also interprets:
50    - '\n', '\r' as a command terminator - no way to escape it,
51    - '<', '>' as redirections,
52    - '|' as pipe operator,
53    - '%var%' as a reference to the environment variable VAR (uppercase),
54      even inside quoted strings,
55    - '&' '[' ']' '{' '}' '^' '=' ';' '!' '\'' '+' ',' '`' '~' for other
56      purposes, according to
57      <http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/cmd.mspx?mfr=true>
58    We quote a string like '%var%' by putting the '%' characters outside of
59    double-quotes and the rest of the string inside double-quotes: %"var"%.
60    This is guaranteed to not be a reference to an environment variable.
61  */
62 # 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!%&'*+,;<=>[]^`{|}~"
63 # define CMD_FORBIDDEN_CHARS "\n\r"
64 #endif
65
66 size_t
67 system_quote_length (enum system_command_interpreter interpreter,
68                      const char *string)
69 {
70   switch (interpreter)
71     {
72 #if !((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__)
73     case SCI_SYSTEM:
74 #endif
75     case SCI_POSIX_SH:
76       return shell_quote_length (string);
77
78 #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
79     case SCI_WINDOWS_CREATEPROCESS:
80       {
81         size_t len = strlen (string);
82         bool quote_around =
83           (len == 0 || strpbrk (string, SHELL_SPECIAL_CHARS) != NULL);
84         size_t backslashes = 0;
85         size_t length = len;
86
87         if (quote_around)
88           length++;
89         for (; len > 0; string++, len--)
90           {
91             char c = *string;
92
93             if (c == '"')
94               length += backslashes + 1;
95             if (c == '\\')
96               backslashes++;
97             else
98               backslashes = 0;
99           }
100         if (quote_around)
101           length += backslashes + 1;
102         return length;
103       }
104
105     case SCI_SYSTEM:
106     case SCI_WINDOWS_CMD:
107       {
108         size_t len = strlen (string);
109         bool quote_around =
110           (len == 0 || strpbrk (string, CMD_SPECIAL_CHARS) != NULL);
111         size_t backslashes = 0;
112         size_t length = len;
113
114         if (quote_around)
115           length++;
116         for (; len > 0; string++, len--)
117           {
118             char c = *string;
119
120             if (c == '"')
121               length += backslashes + 1;
122             if (c == '%')
123               length += backslashes + 2;
124             if (c == '\\')
125               backslashes++;
126             else
127               backslashes = 0;
128           }
129         if (quote_around)
130           length += backslashes + 1;
131         return length;
132       }
133 #endif
134
135     default:
136       /* Invalid interpreter.  */
137       abort ();
138     }
139 }
140
141 char *
142 system_quote_copy (char *p,
143                    enum system_command_interpreter interpreter,
144                    const char *string)
145 {
146   switch (interpreter)
147     {
148 #if !((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__)
149     case SCI_SYSTEM:
150 #endif
151     case SCI_POSIX_SH:
152       return shell_quote_copy (p, string);
153
154 #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
155     case SCI_WINDOWS_CREATEPROCESS:
156       {
157         size_t len = strlen (string);
158         bool quote_around =
159           (len == 0 || strpbrk (string, SHELL_SPECIAL_CHARS) != NULL);
160         size_t backslashes = 0;
161
162         if (quote_around)
163           *p++ = '"';
164         for (; len > 0; string++, len--)
165           {
166             char c = *string;
167
168             if (c == '"')
169               {
170                 size_t j;
171
172                 for (j = backslashes + 1; j > 0; j--)
173                   *p++ = '\\';
174               }
175             *p++ = c;
176             if (c == '\\')
177               backslashes++;
178             else
179               backslashes = 0;
180           }
181         if (quote_around)
182           {
183             size_t j;
184
185             for (j = backslashes; j > 0; j--)
186               *p++ = '\\';
187             *p++ = '"';
188           }
189         *p = '\0';
190         return p;
191       }
192
193     case SCI_SYSTEM:
194     case SCI_WINDOWS_CMD:
195       {
196         size_t len = strlen (string);
197         bool quote_around =
198           (len == 0 || strpbrk (string, CMD_SPECIAL_CHARS) != NULL);
199         size_t backslashes = 0;
200
201         if (quote_around)
202           *p++ = '"';
203         for (; len > 0; string++, len--)
204           {
205             char c = *string;
206
207             if (c == '"')
208               {
209                 size_t j;
210
211                 for (j = backslashes + 1; j > 0; j--)
212                   *p++ = '\\';
213               }
214             if (c == '%')
215               {
216                 size_t j;
217
218                 for (j = backslashes; j > 0; j--)
219                   *p++ = '\\';
220                 *p++ = '"';
221               }
222             *p++ = c;
223             if (c == '%')
224               *p++ = '"';
225             if (c == '\\')
226               backslashes++;
227             else
228               backslashes = 0;
229           }
230         if (quote_around)
231           {
232             size_t j;
233
234             for (j = backslashes; j > 0; j--)
235               *p++ = '\\';
236             *p++ = '"';
237           }
238         *p = '\0';
239         return p;
240       }
241 #endif
242
243     default:
244       /* Invalid interpreter.  */
245       abort ();
246     }
247 }
248
249 char *
250 system_quote (enum system_command_interpreter interpreter,
251               const char *string)
252 {
253   switch (interpreter)
254     {
255 #if !((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__)
256     case SCI_SYSTEM:
257 #endif
258     case SCI_POSIX_SH:
259       return shell_quote (string);
260
261 #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
262     case SCI_WINDOWS_CREATEPROCESS:
263     case SCI_SYSTEM:
264     case SCI_WINDOWS_CMD:
265       {
266         size_t length = system_quote_length (interpreter, string);
267         char *quoted = XNMALLOC (length, char);
268         system_quote_copy (quoted, interpreter, string);
269         return quoted;
270       }
271 #endif
272
273     default:
274       /* Invalid interpreter.  */
275       abort ();
276     }
277 }
278
279 char *
280 system_quote_argv (enum system_command_interpreter interpreter,
281                    char * const *argv)
282 {
283   if (*argv != NULL)
284     {
285       char * const *argp;
286       size_t length;
287       char *command;
288       char *p;
289
290       length = 0;
291       for (argp = argv; ; )
292         {
293           length += system_quote_length (interpreter, *argp) + 1;
294           argp++;
295           if (*argp == NULL)
296             break;
297         }
298
299       command = XNMALLOC (length, char);
300
301       p = command;
302       for (argp = argv; ; )
303         {
304           p = system_quote_copy (p, interpreter, *argp);
305           argp++;
306           if (*argp == NULL)
307             break;
308           *p++ = ' ';
309         }
310       *p = '\0';
311
312       return command;
313     }
314   else
315     return xstrdup ("");
316 }