exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / system-quote.c
blobdb1c7a32563f0ab5cbc18f25956d600915d1dd1b
1 /* Quoting for a system command.
2 Copyright (C) 2012-2024 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2012.
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.
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.
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/>. */
18 #include <config.h>
20 /* Specification. */
21 #include "system-quote.h"
23 #include <stdlib.h>
24 #include <string.h>
26 #include "sh-quote.h"
27 #include "xalloc.h"
29 #if 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 through wildcard expansion in the
45 callee: By default, in the callee, the initialization code before main()
46 takes the result of GetCommandLine(), wildcard-expands it, and passes it
47 to main(). The exceptions to this rule are:
48 - programs that inspect GetCommandLine() and ignore argv,
49 - mingw programs that have a global variable 'int _CRT_glob = 0;',
50 - Cygwin programs, when invoked from a Cygwin program.
52 # 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*?"
53 # 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 /* Copies the quoted string to p and returns the number of bytes needed.
56 If p is non-NULL, there must be room for system_quote_length (string)
57 bytes at p. */
58 static size_t
59 windows_createprocess_quote (char *p, const char *string)
61 size_t len = strlen (string);
62 bool quote_around =
63 (len == 0 || strpbrk (string, SHELL_SPECIAL_CHARS) != NULL);
64 size_t backslashes = 0;
65 size_t i = 0;
66 # define STORE(c) \
67 do \
68 { \
69 if (p != NULL) \
70 p[i] = (c); \
71 i++; \
72 } \
73 while (0)
75 if (quote_around)
76 STORE ('"');
77 for (; len > 0; string++, len--)
79 char c = *string;
81 if (c == '"')
83 size_t j;
85 for (j = backslashes + 1; j > 0; j--)
86 STORE ('\\');
88 STORE (c);
89 if (c == '\\')
90 backslashes++;
91 else
92 backslashes = 0;
94 if (quote_around)
96 size_t j;
98 for (j = backslashes; j > 0; j--)
99 STORE ('\\');
100 STORE ('"');
102 # undef STORE
103 return i;
106 /* The native Windows cmd.exe command interpreter also interprets:
107 - '\n', '\r' as a command terminator - no way to escape it,
108 - '<', '>' as redirections,
109 - '|' as pipe operator,
110 - '%var%' as a reference to the environment variable VAR (uppercase),
111 even inside quoted strings,
112 - '&' '[' ']' '{' '}' '^' '=' ';' '!' '\'' '+' ',' '`' '~' for other
113 purposes, according to
114 <https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/cmd.mspx?mfr=true>
115 We quote a string like '%var%' by putting the '%' characters outside of
116 double-quotes and the rest of the string inside double-quotes: %"var"%.
117 This is guaranteed to not be a reference to an environment variable.
119 # 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!%&'*+,;<=>?[]^`{|}~"
120 # define CMD_FORBIDDEN_CHARS "\n\r"
122 /* Copies the quoted string to p and returns the number of bytes needed.
123 If p is non-NULL, there must be room for system_quote_length (string)
124 bytes at p. */
125 static size_t
126 windows_cmd_quote (char *p, const char *string)
128 size_t len = strlen (string);
129 bool quote_around =
130 (len == 0 || strpbrk (string, CMD_SPECIAL_CHARS) != NULL);
131 size_t backslashes = 0;
132 size_t i = 0;
133 # define STORE(c) \
134 do \
136 if (p != NULL) \
137 p[i] = (c); \
138 i++; \
140 while (0)
142 if (quote_around)
143 STORE ('"');
144 for (; len > 0; string++, len--)
146 char c = *string;
148 if (c == '"')
150 size_t j;
152 for (j = backslashes + 1; j > 0; j--)
153 STORE ('\\');
155 if (c == '%')
157 size_t j;
159 for (j = backslashes; j > 0; j--)
160 STORE ('\\');
161 STORE ('"');
163 STORE (c);
164 if (c == '%')
165 STORE ('"');
166 if (c == '\\')
167 backslashes++;
168 else
169 backslashes = 0;
171 if (quote_around)
173 size_t j;
175 for (j = backslashes; j > 0; j--)
176 STORE ('\\');
177 STORE ('"');
179 return i;
182 #endif
184 size_t
185 system_quote_length (enum system_command_interpreter interpreter,
186 const char *string)
188 switch (interpreter)
190 #if !(defined _WIN32 && ! defined __CYGWIN__)
191 case SCI_SYSTEM:
192 #endif
193 case SCI_POSIX_SH:
194 return shell_quote_length (string);
196 #if defined _WIN32 && ! defined __CYGWIN__
197 case SCI_WINDOWS_CREATEPROCESS:
198 return windows_createprocess_quote (NULL, string);
200 case SCI_SYSTEM:
201 case SCI_WINDOWS_CMD:
202 return windows_cmd_quote (NULL, string);
203 #endif
205 default:
206 /* Invalid interpreter. */
207 abort ();
211 char *
212 system_quote_copy (char *p,
213 enum system_command_interpreter interpreter,
214 const char *string)
216 switch (interpreter)
218 #if !(defined _WIN32 && ! defined __CYGWIN__)
219 case SCI_SYSTEM:
220 #endif
221 case SCI_POSIX_SH:
222 return shell_quote_copy (p, string);
224 #if defined _WIN32 && ! defined __CYGWIN__
225 case SCI_WINDOWS_CREATEPROCESS:
226 p += windows_createprocess_quote (p, string);
227 *p = '\0';
228 return p;
230 case SCI_SYSTEM:
231 case SCI_WINDOWS_CMD:
232 p += windows_cmd_quote (p, string);
233 *p = '\0';
234 return p;
235 #endif
237 default:
238 /* Invalid interpreter. */
239 abort ();
243 char *
244 system_quote (enum system_command_interpreter interpreter,
245 const char *string)
247 switch (interpreter)
249 #if !(defined _WIN32 && ! defined __CYGWIN__)
250 case SCI_SYSTEM:
251 #endif
252 case SCI_POSIX_SH:
253 return shell_quote (string);
255 #if defined _WIN32 && ! defined __CYGWIN__
256 case SCI_WINDOWS_CREATEPROCESS:
257 case SCI_SYSTEM:
258 case SCI_WINDOWS_CMD:
260 size_t length = system_quote_length (interpreter, string) + 1;
261 char *quoted = XNMALLOC (length, char);
262 system_quote_copy (quoted, interpreter, string);
263 return quoted;
265 #endif
267 default:
268 /* Invalid interpreter. */
269 abort ();
273 char *
274 system_quote_argv (enum system_command_interpreter interpreter,
275 char * const *argv)
277 if (*argv != NULL)
279 char * const *argp;
280 size_t length;
281 char *command;
282 char *p;
284 length = 0;
285 for (argp = argv; ; )
287 length += system_quote_length (interpreter, *argp) + 1;
288 argp++;
289 if (*argp == NULL)
290 break;
293 command = XNMALLOC (length, char);
295 p = command;
296 for (argp = argv; ; )
298 p = system_quote_copy (p, interpreter, *argp);
299 argp++;
300 if (*argp == NULL)
301 break;
302 *p++ = ' ';
304 *p = '\0';
306 return command;
308 else
309 return xstrdup ("");