maint: update copyright
[gnulib.git] / lib / system-quote.c
1 /* Quoting for a system command.
2    Copyright (C) 2012-2014 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
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
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      <http://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
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
186 system_quote_length (enum system_command_interpreter interpreter,
187                      const char *string)
188 {
189   switch (interpreter)
190     {
191 #if !((defined _WIN32 || 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 __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 *
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 __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 __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 *
245 system_quote (enum system_command_interpreter interpreter,
246               const char *string)
247 {
248   switch (interpreter)
249     {
250 #if !((defined _WIN32 || 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 __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 *
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 }