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